홈강좌LAYA 결정 모델 24편 중 12편째
내 LAYA 를 API 로 — laya-serve 로 로컬 서버 띄우기
laya-serve 는 LAYA 에 들어 있는 HTTP 서버다. 환경 변수 셋으로 내가 학습한 v2 를 tickets 라는 이름으로 올리고, API 키를 걸고, curl 로 /v1/systemone 에 묻는다. 모델 이름을 잘못 쓰면 오류 없이 기본 모델로 가는 함정도 실제로 본다.
01 서버를 띄우는 스크립트
#!/bin/sh
# 내 체크포인트(v2)를 laya-serve 로 127.0.0.1:8000 에 띄우고, 요청을 몇 개 보내 본 뒤 끈다.
export LAYA_HOST=127.0.0.1
export LAYA_PORT=8000
: "${LAYA_API_KEY:=$(python -c 'import secrets; print(secrets.token_hex(16))')}" # 밖에서 주지 않으면 이번 실행용 키를 만든다
export LAYA_API_KEY
export LAYA_EXTRA_MODELS='{"tickets": "checkpoints/v2"}'
laya-serve > server.log 2>&1 &
SERVER=$!
for i in $(seq 1 90); do
curl -s -o /dev/null localhost:8000/health && break
sleep 1
done
echo "== health (no key)"
curl -s localhost:8000/health; echo
echo "== no key -> 401"
curl -s -o /dev/null -w "%{http_code}\n" localhost:8000/v1/systemone -H 'content-type: application/json' -d @request.json
echo "== with key, model tickets"
curl -s localhost:8000/v1/systemone \
-H 'content-type: application/json' \
-H "authorization: Bearer $LAYA_API_KEY" \
-d @request.json | python -m json.tool
echo "== typo in model name (tikets)"
sed 's/"tickets"/"tikets"/' request.json | curl -s localhost:8000/v1/systemone \
-H 'content-type: application/json' \
-H "authorization: Bearer $LAYA_API_KEY" \
-d @- | python -c 'import json,sys; r=json.load(sys.stdin); print("routed to:", r["routing"]["model"], "-", r["routing"]["reason"]); print("team:", r["answers"]["team"]["choice"])'
kill $SERVER
wait $SERVER 2>/dev/null || trueLAYA_HOST=127.0.0.1— 이 컴퓨터 안에서만 받는다. 기본값은0.0.0.0(모든 네트워크)이라, 노트북에서 시험할 때는 꼭 바꾼다.LAYA_API_KEY— 값을 주면 모든 요청에Authorization: Bearer <키>가 있어야 한다. 주지 않으면 인증 없이 열린다(공식 문서). 스크립트는 밖에서 키를 주지 않으면 실행할 때마다 무작위 키를 만든다(secrets.token_hex). 파일에 키 값을 적어 두지 않기 위해서다. 실제 서비스의 키는 파일·비밀 저장소로 넘긴다(15편).LAYA_EXTRA_MODELS—{이름: 체크포인트}로 내 모델을 등록한다. 10편에서 만들고 11편에서 승격한 v2 를tickets라는 이름으로 올렸다.
나머지는 서버가 뜰 때까지 /health 를 기다렸다가 요청 셋을 보내고 끈다. 요청 본문은 이렇다.
{
"model": "tickets",
"state": "결제창이 계속 로딩만 되고 넘어가지 않아요. 빨리 확인 부탁드려요.",
"questions": {
"team": {
"type": "choice",
"instructions": "이 문의를 맡을 팀은?",
"criteria": {
"billing": "결제·환불·쿠폰·영수증",
"delivery": "배송·교환·반품",
"account": "로그인·비밀번호·회원·포인트",
"technical": "앱·웹사이트 오류, 결제창 오류 포함"
}
},
"urgent": {"type": "noul", "instructions": "고객이 서두르거나 불편을 드러내는가?"}
}
}model 칸에 등록한 이름(tickets)을 쓴다. 질문은 학습 때와 똑같은 문장과 선택지다(6편).
02 실행
sh serve.sh== health (no key)
{"status":"ok"}
== no key -> 401
401
== with key, model tickets
{
"model": "laya-rl-agent",
"answers": {
"team": {
"type": "choice",
"choice": "technical",
"probabilities": {
"billing": 0.0,
"delivery": 0.0,
"account": 0.0,
"technical": 1.0
},
"confidence": 1.0,
"answer_confidence": 1.0,
"action": {
"act_probability": 1.0
},
"x_jev_confidence": 1.0
},
"urgent": {
"type": "noul",
"noul": 1.0,
"confidence": 1.0,
"answer_confidence": 1.0,
"action": {
"act_probability": 1.0
}
}
},
"usage": {
"input_tokens": 180,
"output_tokens": 0,
"state_tokens": 30,
"state_tokens_dropped": 0,
"truncated": false,
"truncated_questions": []
},
"routing": {
"model": "tickets",
"repo": "checkpoints/v2",
"reason": "explicit model='tickets'",
"detection": null,
"workflow": null
}
}열려 있는 것과 닫힌 것
/health는 키 없이도{"status":"ok"}만 돌려준다. 로드 밸런서·컨테이너가 살아 있는지 확인하는 용도다. 키를 보내면 어떤 판이 올라가 있는지 같은 정보가 더 붙는다.- 키 없이
/v1/systemone을 부르면 401 이다.
응답
3편의 predict 결과와 같은 모양이 HTTP 로 왔다.
answers.team.choice가technical이다. "결제창이 계속 로딩만 되고" 는 결제 낱말이 들어 있지만 회사 규칙으로는 기술팀이다. 학습 전 기본 모델이 가장 많이 틀린 유형인데, v2 는 확신 1.0 으로 맞혔다.x_jev_confidence는 JEV 클라이언트와 맞추려고 붙는 칸이다(1편). JEV 를 쓰던 코드가 주소만 바꿔 옮겨 올 수 있는 이유다.usage의state_tokens_dropped: 0·truncated: false는 문장이 잘리지 않았다는 뜻이다. 긴 문서를 보내면 여기서 잘린 양을 알려 준다.routing.reason: explicit model='tickets'— 등록한 v2(checkpoints/v2)가 답했다.
03 실습 — 모델 이름을 잘못 쓰면
마지막 요청은 tickets 를 tikets 로 잘못 쓴 것이다.
== typo in model name (tikets)
routed to: multilingual - non-Latin script (hangul, 100% of letters); the English checkpoint cannot read it
team: technical오류가 나지 않았다. 서버는 모르는 이름을 "모델을 지정하지 않음" 으로 보고, 언어를 보고 공개 다국어 체크포인트로 보냈다. JEV 클라이언트가 보내는 jev-1 같은 이름을 받아 주려는 동작이다(서버 소스에 이유가 적혀 있다). 답도 technical 로 같아 보이니 알아채기 어렵다. 하지만 이 문의는 학습하지 않은 모델이 판단한 것이다.
그래서 클라이언트는 응답의 routing.model 이 기대한 이름(tickets)인지 확인한다. 다르면 로그를 남기고 알린다. 판을 바꾸는 15편에서도 같은 확인으로 "지금 어느 판이 답하는지" 를 본다.
다음 편에서 이 서버를 어디에 올릴지 고르고, 14편에서 컨테이너 이미지로 만든다.