본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 25. 04:52

Nebius Serverless를 활용한 수영 훈련용 근거 기반 LLM 플래너 구축

요약

Nebius Serverless를 활용하여 수영 훈련을 위한 근거 기반 LLM 플래너를 구축하는 방법을 소개합니다. 타임아웃 문제를 해결하기 위해 Serverless Job과 Endpoint를 결합하여 지식 베이스 구축 및 API 제공 파이프라인을 구현했습니다.

핵심 포인트

  • Nebius Serverless를 이용한 장시간 실행 가능한 AI 파이프라인 구축
  • Serverless Job으로 코칭 지식 베이스 큐레이션
  • Serverless Endpoint를 통한 실시간 훈련 계획 API 제공
  • Vercel/Lambda의 타임아웃 한계를 극복하는 아키텍처 설계

수영은 구조화된 훈련이 측정 가능한 차이를 만들어내는 몇 안 되는 스포츠 중 하나입니다. 잘못된 훈련 구조는 발전을 더디게 하고, 되돌리기 어려운 나쁜 습관을 만듭니다. LA2028 올림픽이 다가오고 경쟁 수영에 대한 관심이 새롭게 높아지면서, 더 많은 아마추어 및 연령대별 수영 선수들이 훈련을 진지하게 받아들이고 있습니다. 문제는 훈련 계획이 항상 유료 장벽 뒤에 있었다는 점입니다. 애플리케이션 비용을 지불하거나, 아니면 스스로 추측해야만 했습니다.

사진 제공: Deepbluemedia

좋은 훈련 계획이란 주 단위로 강도를 어떻게 분배할지, 한 세션에서 어떤 드릴 (drills)을 함께 짝지을지에 대한 결정이며, 왜 지금 이 동작을 하고 있는지 설명해 주는 코칭 레이어 (coaching layer)를 포함합니다. 이는 현대의 LLM (Large Language Models)이 잘 처리할 수 있는 구조적 추론 작업의 전형적인 형태이지만, 반드시 실제 코칭 지식에 근거 (grounded)해야만 합니다. 이 프로젝트는 그러한 종류의 계획 수립 기능을 모든 수영 선수가 사용할 수 있고, 모든 코치가 자신의 전문 지식으로 형성할 수 있는 서버리스 API (serverless API)에 담았습니다.

GitHub repo: https://github.com/andreiparfenov/swimming-coach-serverless
End user demo: https://swimming-coach-iota.vercel.app

이 API를 위해 Vercel이나 Lambda를 사용할 계획이었으나, 이들의 요청당 타임아웃 (per-request timeouts)은 몇 분 동안 실행되는 파이프라인 (pipeline)에는 적합하지 않았습니다. 그래서 대신 장시간 실행 가능한 Serverless Endpoint를 시도했습니다.

이 시스템은 함께 작동하는 두 가지 Nebius Serverless AI 구성 요소로 구축되었습니다. 바로 큐레이션된 코칭 지식 베이스를 구축하는 Serverless Job과 실제 계획 API를 제공하는 Serverless Endpoint입니다. 이제 각 부분이 실제로 어떻게 구축되고 배포되었는지 명령어를 포함하여 살펴보겠습니다. 이를 통해 여러분도 직접 따라 하거나 자신의 프로젝트에 맞게 조정할 수 있을 것입니다.

(https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwa94t2er8nmy5h0hpgkx.png)

Step 1: 먼저 로컬에서 플래닝 API 구축하기

Nebius Serverless 인프라를 건드리기 전에, 애플리케이션이 여러분의 로컬 머신에서 완전히 작동하는지 확인해야 합니다. 앱 자체는 세 개의 체이닝된 에이전트 호출 (chained agent calls)을 포함하는 작은 FastAPI 서비스입니다:

async def generate_plan(profile: SwimmerProfile) -> TrainingPlan:
    macro = await run_periodization_agent(profile)
    weeks_raw = await run_session_agent(profile, macro)
...

주기화 에이전트 (periodization agent)는 수영 선수의 프로필을 가져와 계획의 매크로 구조(주간 테마, 강도, 볼륨 곡선)를 결정합니다. 세션 생성기 (session generator)는 매주 한 번 실행되어 준비 운동, 메인 세트, 정리 운동을 수영장 레인 단위로 채웁니다. 코칭 노트 에이전트 (coaching notes agent)는 모든 세션을 하나의 호출로 배치 처리하여 각 세션에 대한 짧은 노트를 작성합니다. 단일 프롬프트에 모든 것을 요청하는 대신 이와 같이 작업을 분할하면, 각 호출의 출력 스키마 (output schema)를 작게 유지할 수 있으며, 문제가 발생했을 때 어떤 단계에서 잘못되었는지 파악하기가 훨씬 쉬워집니다.

로컬에서 실행하려면:

cp env.example .env
# .env 파일에 NEBIUS_API_KEY를 입력하세요

...

Token Factory API 키가 없다면, https://tokenfactory.nebius.com에 접속하여 로그인한 후, API keys 항목에서 키를 생성하세요. 키는 한 번만 표시되므로 즉시 복사해 두어야 합니다. NEBIUS_API_KEY는 모든 에이전트 호출을 인증하는 데 사용됩니다.

그 다음 테스트를 진행합니다:

curl -X POST http://localhost:8000/generate-plan \
  -H "Content-Type: application/json" \
  -d '{
...

Step 2: 엔드포인트 컨테이너화 및 배포

로컬에서 작동한다면, 다음 단계는 Nebius를 위해 패키징하는 것입니다.

Nebius CLI를 설치하고 인증(nebius auth login)해야 하며, 이미지를 푸시할 레지스트리에 Docker 로그인을 완료(docker login)해야 합니다. 두 작업 모두 한 번만 설정하면 되는 단계입니다.

패키징을 시작해 보겠습니다:

docker buildx build --platform linux/amd64 -t docker.io/<your-username>/swimcoach-api:latest --push .

Nebius 엔드포인트 (Endpoints)는 연결할 서브넷 (Subnet)이 필요합니다. 서브넷이 하나뿐이라면 자동으로 선택될 수 있습니다:

SUBNET_ID=$(nebius vpc subnet list --format jsonpath='{.items[0].metadata.id}')

그 다음 엔드포인트 자체를 생성합니다:

nebius ai endpoint create \
  --name swimcoach-api \
  --image docker.io/<your-username>/swimcoach-api:latest \
...

이 경우에는 CPU 프리셋 (Preset)만으로도 충분합니다. 엔드포인트는 모델을 로컬에서 실행하지 않고 Token Factory로의 호출을 오케스트레이션 (Orchestrate)만 하기 때문에, GPU 비용을 지불할 이점이 없습니다. cpu-e2 / 2vcpu-8gb는 단순히 이 워크로드 (Workload)에 필요한 사양입니다. 만약 다른 규모로 설정하고 싶다면 nebius ai endpoint create --help를 실행하여 전체 플랫폼 및 프리셋 범위를 확인할 수 있습니다.

2~3분 후, 엔드포인트 상태가 Running으로 변경되며 공개 주소가 출력됩니다:

nebius ai endpoint get-by-name --name swimcoach-api --format json
curl http://<public-ip>:8000/health
# {"status": "ok"}

Step 3: Serverless Job으로 지식 베이스 구축하기

언어 모델은 정확한 훈련량 대신 그럴듯하게 들리는 훈련량을 기꺼이 생성해낼 수 있습니다. 따라서 플래닝 에이전트 (Planning agents)가 무언가를 생성하도록 요청받기 전에, 그들의 결정을 근거 지을 수 있는 실제 코칭 지식의 원천이 필요합니다.

이 지점에서 두 번째 Nebius 프리미티브 (primitive)를 사용하게 됩니다. **Serverless Job (서버리스 잡)**은 한 번 실행되고 오프라인으로 작동하며, 작업 완료 후 계속 실행 상태를 유지할 필요가 없기 때문에 이와 같은 작업에 매우 적합합니다. 작업이 완료되면 컴퓨팅 자원은 자동으로 해제됩니다. 이 잡(job)은 job/seed_data/training_principles.json (이미 리포지토리에 포함됨)에서 12개의 수동 작성된 코칭 프로필(수영 선수 수준과 훈련 목표의 각 조합당 하나씩)을 로드하며, 각 프로필에 대해 Token Factory를 한 번 호출하여 구조화된 데이터를 프롬프트 주입 (prompt injection)에 적합한 짧은 코칭 요약본으로 압축합니다. 결과물은 knowledge_base.json에 기록되어 Nebius Object Storage (오브젝트 스토리지)로 업로드됩니다.

시드 데이터 (seed data)는 근거 (grounding)가 실제로 의미가 있는지를 결정하는 핵심 부분입니다. training_principles.json은 USMS의 무료 훈련 리소스나 British Swimming/Swim England의 공개된 코치 교육 자료와 같이 널리 출판되고 공개적으로 이용 가능한 수영 코칭 방법론을 반영하도록 초안이 작성되었습니다.

잡 (Job)에는 Object Storage를 위한 자체 자격 증명 (credentials)이 필요합니다. 정적 액세스 키 (static access key)는 서비스 계정 (service account)에 연결되므로, 먼저 각각 하나씩 생성하십시오:

PARENT_ID=<your-project-id> # `nebius profile list`를 통해 찾은 후,
                            # ~/.nebius/config.yaml에서 해당 프로필의
                            # parent-id 필드를 확인하세요.

...

준비가 되었다면:

cd job
export NEBIUS_API_KEY=<your-tokenfactory-key>
export S3_ACCESS_KEY=<from-the-access-key-output-above>
...
nebius ai job create \
  --name swimcoach-data-processor \
  --image docker.io/<your-username>/swimcoach-job:latest \
...

다음 명령어로 상태를 확인합니다:

nebius ai job logs <job-id> --follow

실행이 성공하면 12개의 프로필이 모두 보강되며, Uploaded to s3://swim-program/knowledge_base.json이라는 메시지와 함께 종료됩니다.

단계 4: Object Storage 권한 장벽

단계 4: Object Storage 권한 장벽

Nebius Object Storage는 서비스 계정(Service Account)이 동일한 프로젝트에 속해 있다는 이유만으로 해당 버킷(Bucket)에 대한 접근 권한을 부여하지 않습니다. 접근 권한은 버킷 정책(Bucket Policy) 규칙을 통해 명시적으로 부여되어야 하며, 해당 규칙은 개별 서비스 계정에 직접 부여할 수 없고 그룹(Group) 단위로만 범위를 지정할 수 있습니다. 따라서 해결 방법은 다음과 같은 짧은 일련의 과정을 거칩니다.

# 1. 스토리지 키가 어떤 서비스 계정에 속해 있는지 확인
nebius iam v2 access-key list --parent-id "$PARENT_ID" --format json
# status.aws_access_key_id를 매칭하고, spec.account.service_account.id를 읽습니다.
...

저는 이 전체 과정을 배포 스크립트인 deploy_job.sh에 통합하였으며, 이 스크립트는 먼저 확인을 수행한 뒤 누락된 요소만 생성하도록 작성되었습니다.

단계 5: 프롬프트에 지식 베이스(Knowledge Base) 연결하기

엔드포인트(Endpoint)는 매 요청마다 Object Storage에서 지식 베이스를 읽어오지 않습니다. 그렇게 하면 거의 변경되지 않는 데이터를 위해 모든 /generate-plan 요청마다 불필요한 네트워크 호출을 추가하게 됩니다. 대신, 컨테이너가 시작된 후 처음 사용할 때 컨테이너 생명 주기(Lifetime)당 한 번씩 knowledge_base.json을 가져오며, 해당 프로세스가 실행되는 동안 메모리에 유지합니다.

def _load() -> dict:
    global _knowledge_base
    if _knowledge_base is None:
...

만약 Object Storage 자격 증명(Credentials)이 구성되지 않았거나 어떤 이유로 가져오기에 실패할 경우, 저장소(Repo)에 커밋된 번들 복사본을 사용하도록 폴백(Fallback) 처리됩니다. 따라서 S3 자격 증명이 전혀 없는 로컬 개발 환경에서도 여전히 작동하며, 짧은 Object Storage 중단이 발생하더라도 엔드포인트가 함께 다운되지 않습니다.

이는 작업(Job)이 실행된 후 지식 베이스를 갱신할 때 재빌드(Rebuild)나 새로운 배포(Deployment)가 필요하지 않음을 의미합니다. 대신 컨테이너를 재시작하여 다음 시작 시 다시 가져오도록 하면 됩니다. Nebius Endpoints는 delete/create와 별개로 stopstart 명령을 통해 정확히 이 기능을 지원합니다.

nebius ai endpoint stop <endpoint-id>
nebius ai endpoint start <endpoint-id>

결과

해당 작업(Job)은 CPU 전용(CPU-only)이며 1분에서 3분 내에 완료됩니다. 마찬가지로 CPU 전용인 엔드포인트(Endpoint)는 /generate-plan 요청당 Token Factory에 대해 5~6회의 순차적 호출을 수행합니다. 주기화(periodization)를 위한 호출 1회, 주차별 호출 1회씩, 그리고 코칭 노트(coaching notes)를 위한 일괄 호출(batch call) 1회가 포함되므로, 전체 지연 시간(latency)은 요청된 주차 수에 따라 늘어납니다. 엔드투엔드(end-to-end)로 측정했을 때, 4주 계획은 약 3분이 소요되며, 2주 계획은 그 절반 정도가 소요됩니다. 이는 의도적인 트레이드오프(trade-off)입니다. 각 단계의 출력값은 작아서 그 자체로 검증하기 쉽지만, 그 대가로 요청 시간이 몇 초가 아닌 몇 분이 걸리게 됩니다.

순수 HTML, CSS, JavaScript로 구성된 작은 정적 프론트엔드(static frontend)는 JSON 응답을 수영 선수가 화이트보드에서 실제로 운동 루틴을 읽는 방식, 즉 웜업(warmup), 메인 세트(main set), 쿨다운(cooldown)으로 구성되며 각 세트가 횟수(reps), 거리(distance), 휴식(rest)으로 작성된 계획으로 변환해 줍니다.

4주 계획을 생성하면 총 약 9,000 토큰에 달하는 7회의 Token Factory 호출이 발생하며, Llama 3.3 70B의 요율인 입력 1M당 $0.13, 출력 1M당 $0.40를 적용하면 계획당 약 0.25센트가 소요됩니다. 엔드포인트(Endpoint) 컨테이너가 주요 비용 동인(cost driver)입니다. 이는 트래픽과 관계없이 지속적으로 실행됩니다. Nebius의 자체 비용 계산기(nebius billing v1alpha1 calculator estimate)로 확인했을 때, 가장 작은 비 GPU 프리셋(cpu-e2 / 2vcpu-8gb)의 컴퓨팅 비용은 하루 약 $1.19입니다.

#NebiusServerlessChallenge

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0