콘텐츠로 이동

스킬 만들기 (@zio_skill)

짜는 법과 돌리는 법을 답하는 묶음입니다. 무엇을 부를 수 있는지(Zio.node · Zio.text · Zio.utils …)는 주제별 문서에 따로 있고, 여기서 다루는 것은 그 함수를 어디에 놓고 어떻게 돌리는가입니다. 어느 것을 쓰든 놓는 자리와 돌리는 길은 같습니다.

플랫폼의 .skills/ 디렉토리 안에 파이썬 스크립트를 작성하고 @zio_skill 데코레이터를 붙이면, 해당 함수는 skillFunction 노드이자 AI 도구로 등록됩니다. 플랫폼 전체를 내렸다 올릴 필요는 없습니다 — 그 스킬 하나만 다시 띄우면 됩니다(아래 「다시 띄우기」).

main.py는 작성할 필요 없습니다!

@zio_skill 함수만 작성하면, 플랫폼이 콜드 스타트 시 자동으로 main.py를 생성하고 가상환경(.venv)도 만듭니다. 둘 다 직접 만들지 마십시오.

같은 폴더 구조로 셋을 만들 수 있습니다. 무엇을 만들지는 누가 부르는가로 정해집니다.

모양 부르는 쪽 폴더에 두는 것
함수만 에이전트 노드 · 다른 스킬 · curl *.py@zio_skill 만. main.py 는 플랫폼이 만듭니다.
서버가 그리는 화면 브라우저 (HTML 을 서버가 만들어 보냄) 직접 짠 main.py — FastAPI 라우트가 HTMLResponse 를 돌려줍니다.
브라우저가 그리는 화면 브라우저 (index.htmlfetch 로 데이터를 받음) 직접 짠 main.py + index.html. 자세한 것은 호스팅 문서.

직접 짠 main.py 를 쓰려면 그 파일 첫머리의 @zio_skill 데코레이터 함수 자동 등록 표시 줄을 지우십시오. 그 줄이 있는 동안에는 플랫폼이 스킬을 띄울 때마다 main.py 를 다시 씁니다 — 고쳐도 되돌아갑니다. 표시 줄을 지운 뒤로는 손대지 않습니다.

.skills/ 아래에 폴더를 하나 만들고, 그 안의 .py 파일에 @zio_skill 함수를 적으면 끝입니다. 폴더 하나가 배포 단위 하나(가상환경 하나)입니다.

.skills/
└── <폴더명>/ ← 배포 단위 하나
├── <아무이름>.py ← @zio_skill 함수를 여기 적는다 (여러 개 가능)
├── requirements.txt ← (선택) 추가 라이브러리
├── main.py ← ⚡ 플랫폼 자동 생성. 직접 만들지 말 것
└── .venv/ ← ⚡ 플랫폼 자동 생성. 직접 만들지 말 것

requirements.txt가상환경을 처음 만들 때 한 번만 읽습니다. (pandas · fastapi · uvicorn · requests 는 적지 않아도 깔립니다.) 나중에 한 줄 더해도 저절로 깔리지 않습니다 — 그때 무엇을 해야 하는지는 아래 「라이브러리를 나중에 더했다면」 에 있습니다.

에이전트 빌더에서 skillFunction 노드를 캔버스에 드래그한 후, 우측 속성 패널의 스킬 경로 입력란에 폴더명/함수명 형식으로 입력합니다:

my_custom_skills/get_random_greeting
my_custom_skills/get_customer_issue_summary

skillFunction은 프레임워크가 런타임에 필요한 데이터를 함수의 인자(Parameter) 이름에 맞추어 자동으로 주입(Inject)합니다.

예약어(파라미터명) 설명 및 활용
input (dict) 워크플로우 캔버스 상에서 바로 이전 노드가 반환한 결과값이 자동으로 들어옵니다. 예: user_name = input.get("username")
state (dict) LangGraph 엔진이 관리하는 워크플로우의 전체 상태(State) 딕셔너리가 통째로 들어옵니다. 이전 흐름의 모든 컨텍스트를 확인할 때 유용합니다.
공유 상태 변수 이전 스킬 노드가 실행 결과로 반환하여 output 공간에 병합해 둔 변수들입니다. 함수의 인자(Parameter) 이름과 상태 키값이 일치할 경우 런타임에 자동으로 해당 값이 주입됩니다.
additional_parameter (str) Agent Builder의 속성 패널 내 additional_parameter 입력란에 작성한 설정값(텍스트 또는 JSON 문자열)이 그대로 주입됩니다.
from zio_ontology import zio_skill, Zio
@zio_skill
def get_customer_issue_summary(input: dict = None, state: dict = None, customer_id: str = None) -> dict:
"""특정 고객의 현재 열려있는(OPEN) 불만 접수 내역을 요약하여 반환합니다."""
cid = customer_id or (input.get("customer_id") if input else None)
if not cid:
return {"error": "고객 ID가 제공되지 않았습니다."}
df = (
Zio.node("Customer")
.where(id=cid)
.out("FILED")
.node("Issue")
.where(status="OPEN")
.fetch()
.to_pandas()
)
if df.empty:
return {"summary": "현재 접수된 불만 내역이 없습니다."}
issue_list = ", ".join(df['title'].tolist())
return {
"open_issues_count": len(df),
"issue_titles": issue_list,
"summary": f"고객님은 현재 {len(df)}건의 미해결 이슈({issue_list})를 가지고 있습니다."
}

스킬은 파일이 아니라 폴더입니다

.skills/ 바로 밑에 놓은 낱개 .py 는 스킬이 아닙니다. 폴더가 있어야 플랫폼이 그 안에 main.py 와 가상환경(.venv)을 만들고, SDK 경로를 꽂아 프로세스를 띄웁니다.

폴더 없이 그냥 실행하면 ModuleNotFoundError: No module named 'zio_ontology' 가 납니다. 코드가 틀린 것이 아니라 띄워 주는 절차를 안 거친 것입니다. 폴더로 옮기면 그대로 돕니다.

SSH 컨테이너에는 파이썬이 깔려 있지 않습니다.python --versionimport zio_ontology 도 여기서는 안 됩니다 — 없는 것이 정상이니 알아볼 필요가 없습니다. 여기는 코드를 적는 자리이고, 코드가 도는 곳은 스킬 러너 컨테이너입니다. 파이썬도, 가상환경도, zio_ontology 모듈도 전부 그쪽에 있습니다.

폴더로 두면 부르는 길이 셋 생깁니다. 셋 다 같은 함수를 부릅니다 — 어느 길로 부르든 결과가 같습니다.

주소는 하나뿐입니다. 포트로는 못 부릅니다

스킬은 스킬 러너라는 또 다른 컨테이너에서 돕니다. SSH 로 붙은 자리에서는 그 컨테이너가 아예 안 보입니다localhost 도, api:<포트> 도 안 열립니다. 포트 번호를 주소에 쓰지 마십시오. 어디에서 봤든 그것은 러너 안에서만 뜻이 있는 번호입니다.

어디서 부르나 주소
내 노트북 브라우저 플랫폼 주소 그대로 — https://<플랫폼 주소>/skills/<폴더명>/docs
SSH 안(터미널·IDE의 AI) http://api:8000/api/artifacts/skills/call/<폴더명>/execute/<함수명>— 늘 이 주소입니다. 바깥 주소(https://…)로는 못 나갑니다.
부르는 길 언제 쓰나
/skills/<폴더명>/docs 브라우저로 열면 함수 목록이 나오고 눌러서 바로 실행됩니다(Swagger). 표 하나 만들기 같은 한 번짜리 작업은 이 길이 제일 빠릅니다. 좌측 스킬 탭에서도 열 수 있습니다.
POST /execute/<함수명> curl 이나 다른 프로그램에서 부를 때. 인자는 JSON 본문의 최상위 열쇠로 찾아 넣습니다 — 함수의 인자 이름과 열쇠 이름이 같으면 됩니다. 브라우저 쪽에서는 앞에 /skills/<폴더명> 이 붙고, SSH 안에서는 앞에 http://api:8000/api/artifacts/skills/call/<폴더명> 이 붙습니다(아래).
skillFunction 노드 에이전트 빌더에서 폴더명/함수명 을 적어 워크플로우에 끼울 때. 배치와 디버거가 지나는 길이 이것입니다.

여기서는 python 을 부를 수 없습니다

섹션 제목: “여기서는 python 을 부를 수 없습니다”

SSH 로 붙은 이 자리에는 **python·python3·pip 이 없습니다.**코드를 쓰는 곳과 도는 곳이 다릅니다 — 당신은 파일만 쓰고, 실행은 다른 컨테이너가 합니다. 그래서 아래가 python main.py 가 아니라 curl 한 줄입니다.

헷갈리기 쉬운 자리가 둘 있습니다. .venv/ 폴더가 보이지만 그 안의 python다른 컨테이너를 가리키는 끊어진 링크이고, main.py 맨 아래의 if __name__ == "__main__": uvicorn.run(...)여기서 부르라는 뜻이 아닙니다 — 러너가 그 파일을 그렇게 띄웁니다. apt install python3 같은 것을 시도하지 마십시오. 필요 없는 일이고 되지도 않습니다.

고친 뒤 돌려 보는 일은 한 줄입니다. 다시 띄우고 부르는 것을 한꺼번에 합니다.

zio run <폴더>/<함수> [JSON] 다시 띄우고 부른다 ← 고친 뒤엔 이것 하나
zio call <폴더>/<함수> [JSON] 부르기만 한다
zio restart <폴더> 다시 띄우기만 한다
zio logs <폴더> [줄수] 안 뜬 까닭 보기
zio deps <폴더> requirements.txt 설치
zio ls 스킬 폴더 목록
zio ping 엔진이 살아 있나
# 예
zio run example_skillFunction/get_recent_customers
zio run my_project/search '{"keyword":"전소영"}'

zio아래 curl 을 감싼 것뿐입니다 — 새로 열리는 문은 없습니다. 데이터는 여전히 스킬 안에서 SDK 로만 읽습니다. 무슨 일이 오가는지 보고 싶거나 zio 가 없는 환경이라면 아래 curl 을 그대로 쓰십시오.

주소는 늘 같습니다. 외울 것은 이 한 줄뿐이고, 띄우는 일은 신경 쓰지 않아도 됩니다 — 안 떠 있으면 부르는 김에 알아서 뜹니다. 처음 한 번만 가상환경을 만드느라 1~2분 걸리고, 그다음부터는 곧바로 답합니다.

주소를 셸 변수에 담지 마십시오. IDE 는 명령을 한 줄씩 따로 돌립니다. 앞 명령에서 URL=$(…) 로 담아도 다음 명령에는 남아 있지 않습니다. 아래처럼 주소를 통째로 적으십시오.

<폴더명><함수명> 을 **당신 것으로 바꿔 넣으십시오.**이 둘은 서로 다른 이름입니다 — 폴더는 배포 단위이고, 함수는 @zio_skill 바로 아래 def 에 적은 이름입니다. 헷갈리면 폴더에서 찾아보십시오(아래 셋째 토막).

# 부른다. 안 떠 있으면 알아서 뜬다 — 이 주소가 전부다
curl -s -X POST http://api:8000/api/artifacts/skills/call/<폴더명>/execute/<함수명> \\
-H "Content-Type: application/json" -d '{}'
# 코드를 고쳤으면 — 이 줄로 다시 띄운 다음, 위를 그대로 다시 부른다
curl -s -X POST http://api:8000/api/artifacts/skills/restart \\
-H "Content-Type: application/json" -d '{"skill_name":"<폴더명>"}'
# 함수 이름이 헷갈리면 — @zio_skill 바로 아래 def 이름이 그것이다
grep -rn -A1 "@zio_skill" /agent_skills/<폴더명>/*.py

고친 뒤에는 다시 띄워야 합니다. 스킬은 한 번 뜨면 그 상태로 계속 돌고, 파일이 바뀌어도 저절로 다시 읽지 않습니다.

다만 이것은 파이썬 코드(.py) 얘기입니다.index.html·CSS·JS 같은 정적 파일은 요청마다 디스크에서 새로 읽어 보내므로, 다시 띄울 필요 없이 브라우저 새로고침이면 바뀝니다. 화면(프론트)만 손봤다면 restart 를 부르지 마십시오 — 부를 함수의 파이썬을 고쳤을 때만 다시 띄웁니다.

거꾸로, 안 고쳤으면 다시 띄우지 마십시오. restart떠 있는 것을 죽이고 새로 띄웁니다 — 함수를 부를 때마다 붙이면 그때마다 프로세스가 갈려 느려질 뿐입니다. 주소는 그대로이니 그냥 부르면 됩니다.

  • SSH 안에서 — 위 「돌려 보기」의 restart입니다. 떠 있으면 죽이고 새로 띄웁니다.
  • 웹 화면에서 — 좌측 스킬 탭에서 그 스킬을 재시작합니다.
  • 브라우저로 /skills/<폴더명>/docs 를 여는 것은 재시작이 아닙니다. 안 떠 있을 때만 띄우고, 돌고 있으면 옛 코드가 그대로 응답합니다.

뜨다가 실패하면 그 폴더의 .skill_stderr.log 에 이유가 적힙니다. SSH 안에서는 이렇게 봅니다. curl -s "http://api:8000/api/artifacts/skills/logs?skill_name=<폴더명>"

떴다고 내 함수가 등록된 것은 아닙니다. 문법 에러는 위처럼 재시작 자체가 실패하지만, @zio_skill 을 안 붙였거나 함수 이름에 오타가 있으면 프로세스는 멀쩡히 뜨고 부를 때서야 404 가 납니다. 그래서 재시작 응답에 지금 등록된 함수 목록(loaded_functions, 이름·시그니처·설명 첫 줄)을 함께 돌려줍니다 — 내가 부르려는 이름이 거기 있는지 먼저 보십시오. 하나도 없으면 warning 이 왜 비었는지 알려 줍니다. (_main 으로 시작하는 파일은 안 읽습니다.)

requirements.txt 는 가상환경을 처음 만들 때 한 번 읽고 맙니다. 이미 있는 가상환경에 새로 적은 것을 깔려면 이 한 줄을 부르고, 그다음 스킬을 다시 띄우십시오.

curl -s -X POST http://api:8000/api/artifacts/skills/install-dependencies -H "Content-Type: application/json" -d '{"skill_name":"<폴더명>"}'

{"status":"success", …} 가 오면 깔린 것입니다. 90초를 넘기면 시간초과로 끊깁니다 — 무거운 라이브러리라면 그때는 그 폴더의 .venv 를 통째로 지우고 다시 띄우는 편이 확실합니다(처음부터 다시 깝니다).