에이픽 / 스킬 / 판매자 센터 / 제작 가이드

스킬 제작 가이드

내용을 넣고, 가격을 정하고, 제출하면 됩니다. 처음이라면 샘플을 불러와 내용만 바꿔 보세요.

시작하기

  1. 판매자 등록 — 판매자 센터에서 약관에 동의하고 정산 계좌를 등록합니다. 한 번만 하면 됩니다.
  2. 실행 방식 고르기 — '새 스킬 만들기'에서 방식을 고릅니다. 지금 새로 제출할 수 있는 것은 프롬프트 방식입니다. 계산·변환처럼 정확해야 하는 일은 프롬프트 스킬의 코드 단계로 넣습니다. 등록한 뒤에는 바꿀 수 없습니다.
  3. 판매 정보 쓰기 — 이름, 한 줄 설명, 자세한 설명, 분류, 주소(영문)를 넣습니다. 구매자가 읽고 무엇을 넣으면 무엇이 나오는지 알 수 있게 씁니다.
  4. 입력·결과 형식 정하기 — 구매자가 넣을 항목과 돌려줄 항목을 정합니다. 결과는 이 형식에 맞을 때만 나가고 결제됩니다.
  5. 비공개 원본 넣기 — 지시문·참고 자료와, 필요하면 코드 단계가 쓸 실행 파일을 넣습니다. 구매자에게는 보이지 않습니다. 참고 자료에는 value-contract.json(전문 판단 규칙·실패 사례·검증 계획)을 함께 넣습니다.
  6. 예제 넣고 시험 실행 — 예제를 1개 이상 넣고, 저장한 초안을 직접 돌려 결과를 확인합니다. 시험 실행은 하루 20회까지 할 수 있습니다.
  7. 가격 정하고 제출 — 1회 실행에 받을 금액을 정해 제출합니다. 심사 기준과 금지 행위를 먼저 확인해 주세요.
  8. 심사와 게시 — 심사를 통과하면 판매가 시작됩니다. 보완 요청을 받으면 의견을 보고 고쳐서 다시 제출합니다.
  9. 고치기 — 게시된 버전은 바뀌지 않습니다. 새 버전을 만들어 고친 뒤 다시 심사를 받으며, 그동안 이전 버전이 계속 판매됩니다.

게시되면 판매 화면(/skills/주소), REST API, MCP 도구에서 같은 가격으로 실행됩니다. 따로 서버를 만들거나 결제를 붙일 필요가 없습니다.

구매자에게 보이는 것보이지 않는 것
이름·설명·분류지시문, 참고 자료, 단계, 숨은 예시, 입력 전달 형식
입력·결과 항목과 예제코드, 내 API 주소, 비밀값
결제 금액(기본·예상), 실행 수·성공률·평균 시간모델 등급, 내가 받을 금액과 적립 내역

실행 방식 고르기

방식등록하는 것맞는 일
프롬프트지시문과 참고 자료평가·분류·요약·교정·글쓰기처럼 판단이 필요한 일
코드Python·Node.js 코드나 셸 스크립트. 파일 여러 개를 함께 올릴 수 있습니다변환·계산·검증처럼 규칙이 정해진 일

실행 방식은 등록한 뒤에는 바꿀 수 없습니다. 다른 방식으로 만들려면 새 스킬로 등록합니다.

지금 새로 제출할 수 있는 것은 프롬프트 방식입니다. AI 를 쓰지 않고 코드만 실행하는 스킬과 내 API 를 부르기만 하는 스킬은 전문 AI 스킬 기준에 따라 제출할 수 없습니다. 코드는 프롬프트 스킬의 코드 단계로 넣어 주세요.

입력·결과 형식

구매자가 넣는 값(입력)과 돌려받는 값(결과)의 항목을 정합니다. 실행 결과가 결과 형식과 맞을 때만 구매자에게 전달되고 판매로 인정됩니다. 형식이 분명할수록 구매자가 프로그램에서 쓰기 쉽습니다.

칸으로 정하기

편집 화면에서 '항목 추가'를 눌러 한 줄씩 채웁니다.

칸쓰는 법
이름영문으로 시작하는 영문·숫자·밑줄. 예: product_name
형식문자열, 정수, 숫자, 참/거짓, 문자열 목록 가운데 하나
설명구매자가 보는 안내. 짧게 쓰면 실행 화면에서 칸 이름으로 쓰입니다.
선택지정해진 값만 받을 때 쉼표로 나눠 씁니다. 예: 정중하게, 친근하게
최대 글자문자열의 길이 상한. 120자 이하면 한 줄 칸, 넘으면 여러 줄 칸으로 보입니다.
필수값이 없으면 실행이 접수되지 않습니다.

JSON 으로 정하기

항목 안에 항목이 들어가거나(객체), 객체의 목록을 돌려주거나, 숫자 범위·날짜 형식을 정하려면 'JSON 으로 편집'을 눌러 직접 씁니다. 최상위는 항상 object 입니다.

{
  "type": "object",
  "properties": {
    "risks": {
      "type": "array",
      "maxItems": 12,
      "description": "위험 조항",
      "items": {
        "type": "object",
        "properties": {
          "clause": {
            "type": "string",
            "description": "조항 번호나 제목"
          },
          "level": {
            "type": "string",
            "enum": [
              "높음",
              "중간",
              "낮음"
            ],
            "description": "위험도"
          },
          "reason": {
            "type": "string",
            "description": "위험한 이유"
          },
          "ask": {
            "type": "string",
            "description": "협의할 내용"
          }
        },
        "required": [
          "clause",
          "level",
          "reason",
          "ask"
        ]
      }
    },
    "summary": {
      "type": "string",
      "maxLength": 400,
      "description": "전체 요약"
    }
  },
  "required": [
    "risks",
    "summary"
  ]
}
쓸 수 있는 것뜻
typeobject · string · number · integer · boolean · array
properties · required객체의 항목과 필수 항목 이름
additionalPropertiestrue 면 정하지 않은 항목도 받습니다. 기본은 받지 않습니다.
items · minItems · maxItems목록 안 값의 형식과 개수 범위
enum정해진 값 가운데 하나
minLength · maxLength문자열 길이 범위
minimum · maximum숫자 범위
format문자열의 형식: email · uri · date · date-time
default실행 화면에 미리 채워 두는 값
title · description구매자에게 보이는 이름과 설명

항목은 50개, 중첩은 5단계까지입니다.

지시문 쓰기

지시문은 AI 모델에게 주는 업무 지침입니다. 20자에서 100,000자까지 쓸 수 있습니다. 구매자의 입력은 지시문과 섞이지 않고 별도의 자료로 넘어가며, 결과 형식은 에이픽이 자동으로 덧붙이므로 지시문에 JSON 모양을 다시 적지 않아도 됩니다.

잘 통하는 구성

  1. 역할과 대상 — 누구의 입장에서 무엇을 다루는지 한 문장으로.
  2. 판단 기준 — 점수·등급·통과 여부를 가르는 기준을 번호를 매겨 구체적으로.
  3. 작성 규칙 — 결과 항목마다 무엇을 어떤 길이와 말투로 쓰는지.
  4. 하지 않을 일 — 지어내지 말아야 할 것, 다루지 않을 범위.
너는 유튜브 제목을 평가하는 편집자다. 입력의 title 을 아래 기준으로 평가한다. topic 이 있으면 그 주제의 시청자를 기준으로 본다.

평가 기준(각 25점)
1. 구체성: 숫자, 고유명사, 상황이 드러나는가
2. 궁금증: 답을 확인하고 싶게 만드는가. 낚시성 과장은 감점한다
3. 대비·변화: 기대와 다른 결과, 전후 비교가 있는가
4. 길이: 공백 포함 18~32자면 만점, 벗어날수록 감점한다

작성 규칙
- score 는 네 기준 점수의 합이다.
- strengths 와 weaknesses 는 각각 1~3개, 한 문장으로 쓴다. 제목에 실제로 있는 표현을 근거로 든다.
- suggestions 는 같은 내용을 유지한 채 고친 제목 3개다. 없는 사실을 지어내지 않는다.

결과가 흔들릴 때

  • "적절히", "잘" 같은 말 대신 숫자와 조건으로 씁니다. 예: "18~32자면 만점".
  • 결과 항목 이름을 지시문에서도 같은 이름으로 부릅니다. 예: "suggestions 는 3개".
  • 늘 같은 답이 필요하면 '결과의 다양성'을 낮음으로 둡니다. 문장을 새로 써야 하면 보통 이상으로 둡니다.
  • 말투와 형식을 고정하려면 숨은 예시를 2~3개 넣습니다.
  • 기준이 길어지면 참고 자료로 떼어 내고, 한 번에 시키기 벅찬 일은 단계로 나눕니다.

모델 등급

  • 경제형 — 빠르고 AI 사용 비용이 낮습니다. 분류·검사·짧은 글에 씁니다.
  • 표준형 — 긴 글을 읽고 따지는 일, 조건이 많은 일에 씁니다.
  • 결과 길이는 따로 제한하지 않습니다. 길이를 정하려면 지시문에 "3문장 이내"처럼 적고, 결과 형식의 최대 글자 수로도 묶을 수 있습니다.

파일로 올리기

이미 만들어 둔 스킬 폴더가 있으면 그대로 올리면 됩니다. 화면에 다시 옮겨 적을 필요가 없습니다.

  • 스킬 ZIP 올리기 — 스킬 폴더를 압축한 .zip 을 올립니다. SKILL.md 는 지시문으로, 나머지 파일은 폴더 안 경로를 이름으로 살려 참고 자료로 들어갑니다(예: references/rules.md).
  • 스킬 폴더 올리기 — 압축하지 않은 폴더를 그대로 고릅니다. PC 브라우저에서 쓸 수 있습니다.
  • 지시문 파일 불러오기 — 글 파일이나 Word 문서 하나를 지시문으로 넣습니다.
  • 참고 자료의 파일 추가 — 여러 파일을 한 번에 고를 수 있고, ZIP 을 올리면 풀어서 넣습니다.
  • SKILL.md 맨 위에 아래처럼 이름과 설명이 있으면, 비어 있는 '이름'과 '한 줄 설명' 칸을 채웁니다.
파일 종류들어가는 방식
글·문서.md .txt .csv .tsv .json .jsonl .yaml .xml .html 등 글로 된 파일은 확장자에 상관없이 그대로 들어갑니다. UTF-8, UTF-16, 한글 완성형(EUC-KR)을 읽습니다.
코드·설정.py .js .ts .sh .sql .toml .ini, 확장자 없는 Makefile 같은 파일도 글로 들어갑니다. 참고 자료로 들어간 파일은 모델이 읽는 자료로 쓰이며 실행되지는 않습니다. scripts/ 폴더의 파일은 실행 파일로 들어가며, 단계 실행의 코드 단계로 돌릴 수 있습니다.
Word 문서.docx 는 본문 글을 뽑아 넣습니다.
그 밖의 파일이미지·PDF·글꼴처럼 글이 아닌 파일은 넣지 않고, 어떤 파일이 빠졌는지 알려 줍니다.

ZIP 은 20MB, 파일 하나는 4MB·100,000자, 참고 자료는 30개까지입니다. 지시문과 참고 자료를 합쳐 150,000자를 넘으면 저장되지 않으니, 실행에 필요한 파일만 남겨 주세요.

my-skill.zip
└── my-skill/
    ├── SKILL.md                지시문
    ├── references/rules.md     참고 자료
    ├── references/guide.docx   참고 자료(본문 글)
    ├── scripts/check.py        실행 파일(코드 단계에서 실행)
    └── assets/logo.png         넣지 않음

--- SKILL.md ---
---
name: 오픈마켓 상품명 규칙 검사
description: 상품명이 등록 규칙에 맞는지 검사하고 고친 상품명을 돌려줍니다.
---
너는 오픈마켓 상품명 검수자다. …

올리면 자동으로 채워지는 칸

칸어디서 가져오나
이름 · 한 줄 설명SKILL.md 머리말의 name · description
주소머리말의 slug, 없으면 영문 이름이나 ZIP·폴더 이름
분류머리말의 category, 없으면 이름과 설명에서 추정
입력·결과 형식 · 예제input_schema.json · output_schema.json · examples.json. 없으면 '요청 한 칸, 결과 한 칸'의 기본 형식으로 시작합니다.
가격·모델 등급·단계·숨은 예시·입력 전달 형식apick.json (아래 '내려받기'로 만든 묶음에 들어 있습니다)

자동으로 채운 뒤에는 어떤 칸을 채웠는지 알려 줍니다. 가격과 예제는 직접 확인해 주세요. 이미 내용을 쓴 화면에 올리면 바꾸기 전에 먼저 묻습니다.

내려받기와 복제

  • ZIP 으로 내려받기 — 편집 화면의 내용을 SKILL.md · 참고 자료 · apick.json 묶음으로 내려받습니다. 내 컴퓨터에서 고친 뒤 다시 올리면 모든 칸이 그대로 채워집니다. 버전 관리나 백업에 쓰세요.
  • 복제 — 판매자 센터의 '복제'를 누르면 같은 내용의 새 초안이 열립니다. 주소만 새로 정하면 됩니다.
  • 끌어다 놓기 — ZIP·폴더·파일을 편집 화면에 끌어다 놓아도 올리기와 같게 처리됩니다.

올린 내용은 파일로 암호화해 보관합니다. 저장한 뒤에도 편집 화면에서 고칠 수 있고, 제출한 버전은 고정됩니다.

참고 자료

규칙집, 용어집, 가격표, 예시 모음처럼 지시문과 함께 모델에 넘길 자료입니다. 30개까지 넣을 수 있고, 지시문과 합쳐 150,000자까지입니다.

  • 자주 바뀌는 내용은 지시문에 섞지 말고 참고 자료로 둡니다. 새 버전을 만들 때 그 파일만 바꾸면 됩니다.
  • 지시문에서 자료를 이름으로 부릅니다. 예: "참고 자료 '상품명 규칙.md' 의 규칙으로 검사한다."
  • 결과에 인용 허용 — 꺼 두면 자료의 문장이 결과에 그대로 나온 실행은 구매자에게 전달하지 않습니다. 금지어 목록이나 답변 문구처럼 결과에 그대로 써야 하는 자료만 켭니다.
  • 자료가 길수록 구매자가 내는 AI 사용 비용이 늘어납니다. 그 스킬에 필요한 부분만 넣습니다.
# 상품명 규칙

## 금지어
최저가, 초특가, 파격, 땡처리, 1위, 최고, 무료배송, 당일발송

## 길이
공백 포함 50자 이하

## 특수문자
같은 특수문자를 연달아 쓰지 않는다(!!, ★★, ~~). 괄호는 한 쌍까지 허용한다.

## 단어 반복
같은 단어를 두 번 이상 쓰지 않는다.

입력 전달 형식

비워 두면 구매자의 입력을 JSON 그대로 모델에 넘깁니다. 전달 형식을 쓰면 입력값을 글 틀에 끼워 넘기므로, 각 값이 무엇인지 모델이 분명히 압니다.

가게 이름: {{store_name}}
별점: {{rating}}점
답글 말투: {{tone}}

고객 리뷰:
{{review}}
  • {{이름}} 자리에 그 입력값이 들어갑니다. 입력 항목에 있는 이름만 쓸 수 있습니다.
  • {{input_json}} 자리에는 입력 전체가 JSON 으로 들어갑니다.
  • 값이 없는 선택 항목은 빈칸이 됩니다.

숨은 예시

구매자에게는 보이지 않고 모델에게만 보여 주는 입력·결과 예시입니다. 결과의 말투, 길이, 항목을 쓰는 방식을 고정하는 데 가장 효과가 큽니다. 5개까지 넣을 수 있습니다.

[
  {
    "input": {
      "product_name": "국산 스테인리스 텀블러 500ml",
      "category": "생활용품"
    },
    "output": {
      "passed": true,
      "issues": [],
      "suggestion": "국산 스테인리스 텀블러 500ml"
    }
  },
  {
    "input": {
      "product_name": "★★초특가★★ 여름 반팔 티셔츠 무료배송",
      "category": "패션"
    },
    "output": {
      "passed": false,
      "issues": [
        "금지어 포함: 초특가",
        "금지어 포함: 무료배송",
        "특수문자 반복: ★★"
      ],
      "suggestion": "여름 반팔 티셔츠"
    }
  }
]
  • 통과하는 경우와 걸리는 경우를 하나씩 넣습니다.
  • 판매 화면의 예제와 같은 내용을 넣지 않습니다. 예제는 구매자가 보는 것이고 숨은 예시는 모델이 보는 것입니다.
  • 입력·결과 형식과 맞지 않으면 저장되지 않습니다.

단계 실행

한 번에 시키면 놓치기 쉬운 일을 2~6단계로 나눕니다. 앞 단계의 결과가 다음 단계로 넘어가고, 마지막 단계의 결과가 스킬의 결과가 됩니다.

단계지시문
extract계약서에서 대금, 손해배상, 지식재산권, 해지, 비밀유지에 해당하는 조항을 모두 찾는다. {"clauses": [{"clause": 조항 번호나 제목, "text": 원문 요지}]} 형식으로 출력한다. 판단은 하지 않는다.
assess앞 단계에서 찾은 조항마다 참고 자료 '검토 기준.md' 로 위험도를 정하고, 이유와 협의할 내용을 한 문장씩 쓴다. 기준에 걸리지 않는 조항은 결과에 넣지 않는다. summary 는 가장 위험한 조항부터 두세 문장으로 쓴다.
  • 위쪽 지시문은 모든 단계에 공통으로 들어갑니다. 역할과 공통 규칙만 적습니다.
  • 중간 단계의 지시문에는 출력할 JSON 의 모양을 적습니다. 마지막 단계는 결과 형식을 자동으로 따릅니다.
  • 단계 이름은 영문 소문자로 시작하는 영문 소문자·숫자·밑줄입니다. 다음 단계는 앞 단계의 결과를 그 이름으로 받습니다.
  • AI 사용 비용과 처리 시간은 모델이 실행하는 단계 수만큼 늘어납니다. 처리 시간 상한을 넉넉히 잡습니다.

코드 단계

단계의 종류를 '코드 실행'으로 바꾸면 그 단계는 모델을 부르지 않고 실행 파일에 올린 스크립트를 돌립니다. 세기·계산·중복 제거·형식 맞추기처럼 정확해야 하는 일은 코드에, 읽고 판단하고 쓰는 일은 모델에 맡깁니다.

단계하는 일
collect코드 실행 — scripts/collect.py
write앞 단계 collect 의 reviews 만 근거로 summary(두 문장 이내), strengths(장점, 최대 3개), complaints(불만, 최대 3개)를 JSON 객체로 쓴다. 해당하는 내용이 없으면 빈 배열로 둔다.
finish코드 실행 — scripts/finish.py
# 코드 단계는 { 'input': 구매자 입력, 'steps': { 앞 단계 이름: 그 결과 } } 를 받는다.
def run(payload):
    reviews = payload['input']['reviews']
    seen = []
    for text in reviews:
        cleaned = ' '.join(str(text).split())
        if cleaned and cleaned not in seen:
            seen.append(cleaned)
    return {'total': len(reviews), 'unique': len(seen), 'reviews': seen[:30]}
  • 코드 단계는 { "input": 구매자 입력, "steps": { 앞 단계 이름: 그 결과 } } 를 받습니다. Python·Node.js 는 run(payload) 가 돌려준 객체가, 셸 스크립트는 표준 출력의 JSON 객체가 그 단계의 결과입니다.
  • 마지막 단계가 코드면 그 결과가 스킬의 결과입니다. 결과 형식에 맞게 돌려줘야 합니다.
  • 코드 단계는 AI 사용 비용이 들지 않습니다. 다만 다음 단계로 넘기는 결과는 2만 자 이하여야 합니다(모델 입력에 붙기 때문입니다).
  • 모델이 실행하는 단계가 하나 이상 있어야 합니다. 코드만 실행한다면 코드 방식으로 등록합니다. 단계는 모두 합쳐 6개, 모델 단계는 4개까지입니다.
  • 실행 파일과 실행 환경의 규칙은 코드 스킬과 같습니다(실행 환경과 제약). 코드 단계에서도 등록한 외부 API 호스트를 호출할 수 있습니다. 코드 단계는 운영자 승인을 받은 판매자 계정에서 쓸 수 있습니다.

코드 스킬

코드 스킬은 내가 올린 코드가 구매자의 입력을 받아 결과를 만들어 돌려주는 스킬입니다. Python·Node.js 는 run(input) 함수가 입력 객체를 받아 결과 객체를 돌려주면 되고, 셸 스크립트는 입력을 읽어 결과 JSON 을 표준 출력에 쓰면 됩니다.

코드 실행(코드 방식, 프롬프트 방식의 코드 단계)은 운영자 승인을 받은 판매자 계정에서 등록할 수 있습니다. 필요하면 고객지원으로 신청해 주세요.

코드만 실행하는 스킬은 지금 새로 제출할 수 없습니다(전문 AI 스킬 기준). 판매할 스킬에서는 코드를 프롬프트 스킬의 코드 단계로 씁니다. 아래의 실행 방법, 미리 설치된 패키지, 외부 API 호출, 실행 환경과 제약은 코드 단계에도 그대로 적용됩니다. 코드 단계의 run() 은 구매자 입력 대신 { "input": 구매자 입력, "steps": { 앞 단계 이름: 결과 } } 를 받습니다.

정하는 것내용
언어Python 3.12, Node.js 22, 셸 스크립트(bash) 가운데 하나. 등록한 뒤에도 새 버전에서 바꿀 수 있습니다.
시작 파일편집 화면의 '시작 파일의 코드' 칸에 넣는 코드입니다. 이름은 언어에 따라 main.py·main.js·main.sh 입니다.
실행 파일시작 파일이 불러 쓸 모듈·스크립트·자료 파일. 30개까지 함께 올립니다.
입력과 결과입력은 입력 형식에 맞는 값만 들어옵니다. 결과는 결과 형식에 맞는 JSON 객체 하나여야 하고, 맞지 않으면 실패로 끝나 결제되지 않습니다.
비밀값API 키처럼 감출 값은 저장한 뒤 편집 화면의 '비밀값'에 등록하고 코드에서는 이름으로 읽습니다. 코드에 직접 적지 않습니다.
외부 API 호스트코드가 호출할 외부 서비스의 호스트 이름. 적지 않으면 인터넷과 통신할 수 없습니다. 외부 API 호출하기

실행은 한 번마다 새로 만든 일회용 격리 환경에서 이루어지고, 끝나면 그 환경은 통째로 사라집니다. 앞 실행이 남긴 파일이나 값은 다음 실행에서 볼 수 없습니다.

Python 실행하기

시작 파일에 def run(input) 을 정의합니다. input 은 구매자가 넣은 값이 든 dict 이고, 돌려준 dict 가 그대로 결과가 됩니다.

import re


def to_snake(name):
    text = re.sub(r'[\s\-]+', '_', name.strip())
    text = re.sub(r'(.)([A-Z][a-z]+)', r'\1_\2', text)
    text = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', text)
    return re.sub(r'_+', '_', text).lower()


def convert(value, counter):
    if isinstance(value, dict):
        result = {}
        for key, item in value.items():
            new_key = to_snake(key)
            if new_key != key:
                counter[0] += 1
            result[new_key] = convert(item, counter)
        return result
    if isinstance(value, list):
        return [convert(item, counter) for item in value]
    return value


def run(input):
    counter = [0]
    data = convert(input['data'], counter)
    return {'data': data, 'renamed': counter[0]}
  • 판 — Python 3.12 입니다. 표준 라이브러리 전체를 쓸 수 있습니다.
  • 결과 — run() 이 돌려준 dict 만 결과입니다. print() 로 찍은 글은 결과에 들어가지 않습니다. 숫자·글·목록·dict 처럼 JSON 으로 바꿀 수 있는 값만 넣습니다(날짜는 글로 바꿔서 넣습니다).
  • 오류 — 예외가 밖으로 나가면 그 실행은 실패로 끝나고 구매자는 결제하지 않습니다. 시험 실행에서는 마지막 오류 한 줄이 표시됩니다. 입력이 잘못된 경우에는 무엇이 잘못됐는지 알 수 있는 문구로 예외를 냅니다.
  • 비밀값 — secret("PARTNER_KEY") 로 읽습니다. 환경변수에는 들어 있지 않습니다.

미리 설치된 패키지

패키지판불러오는 이름쓰임
numpy2.1.3import numpy수치 계산
pandas2.2.3import pandas표 자료(CSV·집계)
python-dateutil2.9.0.post0import dateutil날짜 해석
PyYAML6.0.2import yamlYAML 읽고 쓰기
beautifulsoup44.12.3import bs4HTML 읽기

이 밖의 패키지는 실행 중에 설치할 수 없습니다(pip 가 없습니다). 순수 Python 으로 된 작은 모듈은 실행 파일로 함께 올려서 쓸 수 있습니다.

import io
import pandas as pd

def run(input):
    frame = pd.read_csv(io.StringIO(input["csv"]))
    totals = frame.groupby("name")["amount"].sum()
    return {"totals": {name: int(value) for name, value in totals.items()}}

파일 여러 개 쓰기

모듈과 자료 파일을 실행 파일에 올리면 작업 폴더(/work)에 경로 그대로 풀립니다. 시작 파일도 같은 폴더에 있으므로 import 와 상대 경로로 바로 불러 씁니다.

main.py                 시작 파일
rules/__init__.py
rules/tax.py
data/rates.json
import json
from lib.text import clean          # lib/text.py (lib/__init__.py 도 함께 올립니다)

RULES = json.load(open("data/rules.json", encoding="utf-8"))

def run(input):
    return {"text": clean(input["text"], RULES)}

Node.js 실행하기

시작 파일에서 exports.run 으로 내보내거나 맨 위에 function run(input) 을 정의합니다. async 함수여도 됩니다. 돌려준 객체가 그대로 결과가 됩니다.

exports.run = function (input) {
    const text = String(input.text);
    const top = input.top || 5;
    const words = text.split(/\s+/).map(function (word) { return word.replace(/[.,!?"'()]/g, ''); }).filter(Boolean);
    const counts = new Map();
    words.forEach(function (word) { counts.set(word, (counts.get(word) || 0) + 1); });
    const topWords = Array.from(counts.entries())
        .sort(function (a, b) { return b[1] - a[1]; })
        .slice(0, top)
        .map(function (entry) { return { word: entry[0], count: entry[1] }; });
    return {
        chars: Array.from(text).length,
        chars_no_space: Array.from(text.replace(/\s/g, '')).length,
        sentences: text.split(/[.!?]+/).filter(function (part) { return part.trim(); }).length,
        top_words: topWords,
    };
};
  • 판 — Node.js 22 입니다. 내장 모듈(fs·path·crypto·zlib 등)을 쓸 수 있고, 모듈 방식은 require(CommonJS) 입니다.
  • 결과 — run() 이 돌려준(또는 Promise 로 넘긴) 객체만 결과입니다. console.log() 로 찍은 글은 결과에 들어가지 않습니다. 결과를 돌려주면 남은 타이머가 있어도 바로 끝납니다.
  • 오류 — 예외를 던지거나 Promise 가 거절되면 실패로 끝나고 구매자는 결제하지 않습니다.
  • 비밀값 — secret('PARTNER_KEY') 로 읽습니다. process.env 에는 들어 있지 않습니다.

미리 설치된 패키지

패키지판쓰임
require('axios')1.7.9HTTP 요청
require('cheerio')1.0.0HTML 읽기
require('csv-parse')5.6.0CSV 읽기
require('dayjs')1.11.13날짜 계산
require('lodash')4.17.21자료 다루기
require('yaml')2.6.1YAML 읽고 쓰기

이 밖의 패키지는 실행 중에 설치할 수 없습니다(npm 이 없습니다). 의존하는 것이 없는 작은 모듈은 실행 파일로 함께 올려 require('./…') 로 쓸 수 있습니다.

const dayjs = require('dayjs');
const { sum } = require('./lib/math');          // 함께 올린 실행 파일 lib/math.js
const table = require('./data/table.json');     // 자료 파일

exports.run = async function (input) {
    return { total: sum(input.items), unit: table.unit, date: dayjs().format('YYYY-MM-DD') };
};

스크립트 실행하기

언어를 셸 스크립트로 고르면 시작 파일(main.sh) 전체가 bash 로 실행됩니다. 함수 약속은 없고, 입력을 읽어 결과 JSON 객체 하나를 표준 출력에 쓰면 됩니다.

#!/bin/bash
# 입력(JSON)은 표준 입력과 $SKILL_INPUT 파일로 들어온다. 결과는 표준 출력에 JSON 객체 하나로 쓴다.
# 진행 기록을 남기려면 표준 오류(>&2)로 쓴다.
set -euo pipefail

python3 scripts/pick.py "$SKILL_INPUT" > /tmp/values.txt
awk -f scripts/stats.awk /tmp/values.txt
항목내용
입력입력(JSON)은 표준 입력으로 들어오고, 같은 내용이 $SKILL_INPUT 파일에도 있습니다. jq -r '.name' "$SKILL_INPUT" 처럼 꺼내 씁니다.
결과표준 출력에 쓴 JSON 객체 하나가 결과입니다. 그 밖의 글이 표준 출력에 섞이면 실패합니다.
진행 기록표준 오류(>&2)로 씁니다. 결과에 들어가지 않습니다.
종료 코드0 이 아니면 실패입니다. 맨 위에 set -euo pipefail 을 두면 중간에 실패한 명령을 놓치지 않습니다.
비밀값$SECRET_이름 으로 읽습니다(예: $SECRET_PARTNER_KEY).
작업 폴더$SKILL_DIR(/work). 함께 올린 실행 파일이 여기에 풀려 있습니다.
쓸 수 있는 명령python3, node, jq, awk, sed, grep, sort, uniq, cut, tr, wc, head, tail, paste, bc, date, base64, sha256sum, find, xargs, gzip, tar 같은 기본 도구
없는 명령curl·wget·ssh·git 같은 통신 도구와 pip·npm·apt·sudo. 외부 호출은 skill-http 명령으로만 합니다.

스킬 안에서 다른 스크립트 부르기

어느 언어로 시작하든 함께 올린 다른 스크립트를 불러 쓸 수 있습니다. 불려 나온 스크립트도 같은 격리 환경 안에서 돌고, 처리 시간·메모리·프로세스 수는 모두 합쳐서 셉니다.

# 셸 스크립트에서
count=$(python3 scripts/count.py < "$SKILL_INPUT")
rows=$(node scripts/rows.js /tmp/rows.csv)

# Python 에서
import subprocess
done = subprocess.run(["bash", "scripts/convert.sh", "/work/data/in.csv"], capture_output=True, text=True, check=True)

// Node.js 에서
const { execFileSync } = require('child_process');
const out = execFileSync('python3', ['scripts/parse.py'], { input: JSON.stringify(input), encoding: 'utf8' });

프롬프트 스킬에서 스크립트를 돌리려면 단계 실행의 코드 단계를 씁니다. 실행 파일에 올린 스크립트를 단계로 지정하면 모델 단계와 섞어 쓸 수 있습니다.

외부 API 호출하기

코드 실행 환경에는 네트워크가 없습니다. 다만 스킬에 등록하고 심사를 통과한 호스트의 HTTP API 는 아래 함수로 호출할 수 있습니다. 그 밖의 인터넷 통신은 어떤 방법으로도 되지 않습니다.

  1. 편집 화면의 외부 API 호스트 칸에 호스트 이름을 한 줄에 하나씩 적습니다(예: api.example.com). 5개까지 등록할 수 있습니다.
  2. API 키는 비밀값으로 등록하고 코드에서 이름으로 읽습니다.
  3. 코드에서 아래 함수로 호출합니다. 어떤 서비스를 왜 부르는지 설명에 적어 두면 심사가 빨라집니다.

Python

def run(input):
    reply = http_request(
        "POST", "https://api.example.com/v1/score",
        headers={"X-Api-Key": secret("PARTNER_KEY")},
        json={"text": input["text"]},      # 글이나 bytes 는 body= 로 보냅니다
        timeout=8,                          # 초
    )
    if not reply.ok:                        # reply.status, reply.headers, reply.text, reply.content, reply.json()
        raise RuntimeError("점수 서비스가 %d 로 답했습니다" % reply.status)
    return {"score": reply.json()["score"]}

# 막히거나 실패하면 HttpError 가 납니다. error.code 로 이유를 알 수 있습니다.

Node.js

exports.run = async function (input) {
    const reply = await httpRequest({
        method: 'POST', url: 'https://api.example.com/v1/score',
        headers: { 'X-Api-Key': secret('PARTNER_KEY') },
        json: { text: input.text },         // 글이나 Buffer 는 body 로 보냅니다
        timeout_ms: 8000,
    });
    if (!reply.ok) throw new Error('점수 서비스가 ' + reply.status + ' 로 답했습니다');
    return { score: reply.json().score };   // reply.status, reply.headers, reply.text, reply.content
};

// axios 와 fetch 도 같은 길로 나갑니다.
const axios = require('axios');
const { data } = await axios.get('https://api.example.com/v1/items', { params: { q: input.q } });
const page = await (await fetch('https://api.example.com/v1/page')).json();

셸 스크립트

# skill-http METHOD URL [-H "이름: 값"]... [-d 본문 | -f 파일] [-t 초] [-s]
body=$(skill-http POST https://api.example.com/v1/score \
    -H "X-Api-Key: $SECRET_PARTNER_KEY" -H "Content-Type: application/json" \
    -d "$(jq -c '{text: .text}' "$SKILL_INPUT")")

# 응답 본문을 표준 출력에 씁니다. -s 를 주면 첫 줄에 상태 코드를 씁니다.
# 상태가 400 이상이면 종료 코드 22, 호출이 막히거나 실패하면 1 이고 이유는 표준 오류에 나옵니다.

제한

항목내용
부를 수 있는 곳등록한 호스트의 https(443 포트) 주소만. 호스트 이름이 글자 그대로 같아야 하며, 하위 도메인은 따로 등록합니다. IP 주소, 내부망 주소, 에이픽의 주소는 등록할 수 없습니다.
메서드GET, POST, PUT, PATCH, DELETE, HEAD
횟수실행 한 번에 5회까지. 막힌 요청도 횟수에 들어갑니다.
크기요청 본문 256KB, 응답 1024KB 까지
시간요청 하나에 10초까지. 스킬의 처리 시간 상한 안에서 끝나야 합니다.
리다이렉트따라가지 않습니다. 3xx 응답을 그대로 돌려줍니다.
헤더Host·User-Agent·Connection·Content-Length 같은 연결용 헤더와 이름에 밑줄(_)이 든 헤더는 보낼 수 없습니다. 응답의 쿠키는 돌려주지 않습니다.
그 밖의 통신소켓, DNS 조회, http(암호화 없음), 다른 포트, urllib·https.request 같은 직접 접속은 모두 되지 않습니다.
오류 코드뜻
HOST_NOT_ALLOWED이 스킬에 등록되지 않은 호스트입니다.
INVALID_REQUESThttps 가 아니거나, 포트·헤더·메서드·주소가 허용되지 않습니다.
LIMIT_EXCEEDED실행 한 번의 호출 횟수를 넘었습니다.
TOO_LARGE요청 본문이나 응답이 상한보다 큽니다.
TIMEOUT정해진 시간 안에 응답이 오지 않았습니다.
BLOCKED_ADDRESS호스트가 접속할 수 없는 주소로 연결됩니다.
NETWORK_ERROR이름을 찾지 못했거나 연결에 실패했습니다.
  • 등록한 호스트 이름은 판매 화면에 표시됩니다. 구매자는 입력이 그 서비스로 전달될 수 있음을 알고 실행합니다.
  • 외부 서비스에는 그 실행을 처리하는 데 필요한 값만 보냅니다. 구매자의 입력을 모으거나 다른 용도로 쓰면 안 됩니다.
  • 호스트를 바꾸거나 더하려면 새 버전을 만들어 다시 심사를 받습니다.

실행 환경과 제약

코드는 에이픽 서버나 다른 실행과 완전히 분리된 일회용 환경에서만 실행됩니다. 아래 상한을 넘으면 그 실행은 강제로 끝나고 실패로 처리됩니다(구매자는 결제하지 않습니다).

항목내용
언어Python 3.12, Node.js 22, bash. 다른 언어의 실행기나 컴파일러는 없습니다.
처리 시간최대 30초. 스킬마다 정한 처리 시간 상한이 이보다 짧으면 그 값이 적용됩니다.
CPU · 메모리코어 1개 분량, 메모리 512MB. 메모리를 넘으면 바로 끝납니다.
프로세스스레드를 합쳐 32개까지
파일쓸 수 있는 곳은 작업 폴더(/work)와 /tmp 뿐이고 각각 100MB 까지입니다. 나머지는 읽기 전용이며, 실행이 끝나면 모두 사라집니다.
결과 크기표준 출력과 결과를 합쳐 1024KB 까지
올리는 파일파일 하나 200KB, 실행 파일 30개, 모두 합쳐 800KB 이하. 글로 된 파일만 올릴 수 있습니다. 경로는 영문·숫자·밑줄·하이픈·점으로 된 이름을 / 로 잇습니다(예: scripts/parse.py).
네트워크없습니다. 등록한 외부 API 호스트만 정해진 함수로 호출할 수 있습니다.
패키지 설치할 수 없습니다. 미리 설치된 패키지(Python·Node.js)와 함께 올린 파일만 씁니다.
장치GPU 를 비롯한 장치를 쓸 수 없습니다.
상태 저장실행 사이에 아무것도 남지 않습니다. 이전 실행의 파일·변수·캐시를 다음 실행에서 쓸 수 없습니다.
환경변수정해진 몇 개(HOME·PATH·LANG·TZ 등)만 있습니다. 비밀값은 환경변수가 아니라 secret()(셸은 $SECRET_이름)로 읽습니다.
시각현재 시각은 정상적으로 읽을 수 있고 시간대는 한국 시간(Asia/Seoul)입니다.
시스템 정보실행 서버의 운영체제·하드웨어·네트워크·다른 프로세스에 관한 정보는 볼 수 없습니다. 이런 값을 묻는 함수는 정해진 값만 돌려주거나 실패합니다. 코드가 이런 값에 의존하지 않게 만듭니다.

상한에 자주 걸리는 스킬은 판매가 중지될 수 있습니다. 시험 실행에서 가장 큰 입력으로 처리 시간과 메모리를 먼저 확인해 주세요.

예제와 시험 실행

예제는 구매자가 실행 전에 보는 입력과 결과입니다. 1개 이상 있어야 제출할 수 있고, 입력·결과 형식과 맞아야 합니다. 실제로 돌려서 나온 결과를 넣는 것이 가장 좋습니다.

시험 실행은 저장한 초안을 그대로 돌려 봅니다. 판매 기록과 적립은 생기지 않습니다.

시험 실행에서 보이는 것해 볼 일
결과가 결과 형식과 다릅니다어긋난 항목이 함께 표시됩니다. 지시문에 그 항목을 쓰는 법을 적거나 결과 형식을 고칩니다.
JSON 객체가 아닌 글결과 항목을 지시문에서 이름으로 부르고, 숨은 예시를 넣습니다.
지시문·참고 자료의 문장이 그대로 들어 있음결과에 써도 되는 자료라면 '결과에 인용 허용'을 켭니다. 지시문의 문장은 결과에 쓰이지 않게 풀어 쓰도록 지시합니다.
처리 시간 상한을 넘음처리 시간 상한을 올리거나 지시문에서 결과 길이·단계 수를 줄입니다.

가격과 적립

가격 칸에는 1회 실행에 내가 받을 금액을 포인트로 넣습니다. 구매자는 여기에 수수료와 그 실행의 AI 사용 비용을 더한 금액을 결제하고, 내가 정한 금액은 성공한 실행마다 그대로 적립됩니다.

구매자 결제가 = 내가 받을 금액 + 수수료(내가 받을 금액의 20%) + 그 실행의 AI 사용 비용

예) 내가 받을 금액 100P, 그 실행의 AI 사용 비용 3P
    구매자 결제 100 + 20 + 3 = 123P   →   내 적립 100P
  • 가격을 넣으면 편집 화면이 구매자 결제가(예상·최대)를 바로 보여 줍니다.
  • AI 사용 비용은 프롬프트 방식에만 있고, 실행마다 실제로 쓴 만큼 계산합니다. 지시문·참고 자료가 길수록, 결과가 길수록, 단계가 많을수록 늘어납니다. 코드·외부 API 방식은 수수료만 더해집니다.
  • 구매자는 실행 전에 예상 금액과 최대 금액을 조회할 수 있고, 최대 금액을 예약한 뒤 실제 금액만 결제합니다.
  • 결과가 정상적으로 나간 실행만 결제되고 적립됩니다. 내가 직접 실행한 건과 환불된 금액은 적립에서 빠집니다.
  • 수수료율은 버전을 만들 때 정해지고 그 버전에는 계속 같은 비율이 적용됩니다.

정산과 출금

판매자로 등록할 때 정산 계좌를 함께 등록합니다. 예금주를 확인한 뒤 그 계좌로 1원을 보내며, 입금 내역의 보낸 사람 에이픽 뒤에 붙은 숫자 4자리를 입력하면 등록이 끝납니다.

유형등록할 때 받는 것정산 방식
개인이름, 본인 명의 계좌신청 금액에서 3.3%(소득세 3% + 지방소득세 0.3%)를 뗀 금액을 입금합니다.
사업자사업자등록번호, 사업자 명의 계좌신청 금액을 공급가액으로 하는 세금계산서(부가세 별도)를 발행하면, 확인한 뒤 입금합니다.
개인    50,000P 신청 → 3.3%(1,650원)를 떼고 48,350원 입금
사업자  50,000P 신청 → 세금계산서 공급가액 50,000원 + 부가세 5,000원
  • 1P 는 1원입니다. 적립금이 50,000P 이상이면 판매자 센터에서 10,000P 단위로 출금을 신청할 수 있습니다.
  • 출금은 신청을 확인한 날(사업자는 세금계산서를 확인한 날)부터 15영업일 안에 입금합니다. 처리 전에는 신청을 취소할 수 있고, 취소하거나 반려되면 금액이 출금 가능 금액으로 돌아옵니다.
  • 정산 계좌는 판매자 센터에서 바꿀 수 있습니다. 새 계좌도 1원 인증을 거치며, 처리 중인 출금 신청이 있는 동안에는 바꿀 수 없습니다.
  • 계좌번호·이름·사업자등록번호는 암호화해 보관하고, 화면에는 일부를 가려서 보여 줍니다.

자세한 조건은 판매자 약관에 있습니다.

금지 행위

아래 행위는 어떤 경우에도 해서는 안 됩니다. 심사에서 발견되면 반려되고, 게시된 뒤에 발견되면 먼저 판매를 중지한 뒤 사유를 알릴 수 있습니다. 모든 실행은 기록되며, 심사를 통과한 뒤에도 다시 검토합니다.

실행 환경을 건드리는 행위

  • 실행 서버·운영체제·하드웨어·네트워크·다른 실행에 관한 정보를 알아내려고 하거나, 알아낸 것을 결과에 싣거나 밖으로 보내는 것
  • 격리 환경을 벗어나거나 권한을 높이려는 시도, 실행 환경의 제한(시간·메모리·파일·통신)을 피하려는 코드
  • 등록하지 않은 곳과 통신하려는 시도, 주소·포트를 훑는 것, 등록한 호스트를 중계 서버로 삼아 다른 곳에 접속하는 것
  • 무한 반복, 프로세스·메모리·파일을 일부러 채우는 것, 채굴처럼 스킬의 목적과 무관하게 자원을 쓰는 것

심사를 속이는 행위

  • 읽기 어렵게 꼬아 놓은 코드, 암호화하거나 인코딩해 숨긴 코드나 지시문
  • 실행 중에 밖에서 코드나 지시문을 받아 와 실행하는 것(심사한 내용과 실제 동작이 달라집니다)
  • 심사할 때와 판매할 때 다르게 동작하게 만드는 것(특정 날짜·입력·횟수에서만 켜지는 동작 포함)
  • 설명·예제와 다른 결과를 내거나, 예제를 실제 결과가 아닌 것으로 꾸미는 것

구매자와 다른 사람에게 해를 끼치는 행위

  • 구매자의 입력이나 결과를 모으거나 보관하거나, 그 실행을 처리하는 것 말고 다른 용도로 쓰거나 밖으로 보내는 것
  • 주민등록번호·건강 정보 같은 고유식별정보·민감정보를 받아 처리하는 것
  • 송금, 구매, 삭제, 계정 변경처럼 되돌릴 수 없는 동작을 구매자 대신 실행하는 것
  • 악성 코드, 다른 서비스의 접근 제한이나 이용 약관을 우회하는 수집·자동화, 스팸·사기·차별·음란물 제작에 쓰이는 것
  • 투자·의료·법률·채용·신용 평가의 최종 판단을 대신하는 것처럼 표시하는 것
  • 결과 안에서 다른 결제나 외부 가입을 유도하는 것, 광고를 끼워 넣는 것

공정하지 않은 행위

  • 권리가 없는 자료·코드·지시문을 쓰는 것, 다른 판매자의 스킬을 베끼는 것
  • 자신 또는 관계된 계정으로 반복 실행해 실행 수·성공률·적립금을 부풀리는 것
  • 다른 스킬의 지시문을 빼내려 하거나, 플랫폼 규칙을 무시하도록 모델을 유도하는 지시문
  • 비밀값이나 인증키를 결과에 싣는 것

위반이 확인되면 해당 스킬의 판매 중지, 확인이 끝날 때까지 적립금 지급 보류, 계약 해지가 이루어질 수 있고, 회사나 다른 사람에게 생긴 손해는 판매자가 배상해야 합니다. 자세한 내용은 판매자 약관 제8조·제14조·제15조에 있습니다.

심사 기준

제출한 버전은 운영자가 직접 확인한 뒤 게시됩니다. 원본(지시문·참고 자료·코드·실행 파일·외부 API 호스트)을 모두 읽고, 예제 입력과 다른 입력으로 직접 실행해 봅니다. 게시된 버전은 바뀌지 않으며, 고치려면 새 버전을 만들어 다시 제출합니다. 그동안 이전 버전은 계속 판매됩니다.

기준확인하는 것
쓸모구매자가 돈을 내고 쓸 만한 결과를 주는가. 무엇을 넣으면 무엇이 나오는지 분명하고, 직접 하기 어렵거나 번거로운 일을 대신하는가. AI 에 그냥 물어보는 것보다 뚜렷이 나은가(아래 전문 AI 스킬 기준).
설명과 실제의 일치이름·설명·예제가 실제 결과와 맞는가. 예제 입력으로 실행하면 예제와 같은 수준의 결과가 나오는가.
형식과 안정성입력·결과 형식이 설명과 맞고, 여러 입력에서 결과 형식에 맞는 결과가 꾸준히 나오는가. 잘못된 입력에 알아볼 수 있게 실패하는가. 처리 시간·메모리 상한 안에서 끝나는가.
안전금지 행위에 해당하는 것이 없는가. 코드가 하는 일이 설명한 범위 안에 있는가. 숨겨진 동작이 없는가.
외부 호출등록한 외부 API 호스트가 그 일에 꼭 필요한가. 누구의 서비스인지 알 수 있는가. 필요한 값만 보내는가. 판매자가 그 서비스를 쓸 권리가 있는가.
권리내가 권리를 가진 자료·코드만 썼는가. 다른 사람의 저작물·상표·개인정보를 침해하지 않는가.
가격가격이 하는 일의 범위에 맞는가. 같은 일을 하는 다른 스킬과 비교해 지나치지 않은가.

전문 AI 스킬 기준

에이픽의 스킬은 AI 가 전문 지침에 따라 판단하는 상품이어야 합니다. AI 를 쓰지 않는 정규식·단순 스크립트나, 일반적인 지시문으로 AI 를 부르기만 하는 상품은 판매할 수 없습니다. 이미 판매 중인 스킬도 같은 기준으로 다시 심사합니다.

  • 제출할 수 있는 형태 — 프롬프트 방식만 새로 제출할 수 있습니다. 코드만 실행하는 스킬과 내 API 를 부르기만 하는 스킬은 제출할 수 없습니다. 계산·정리 같은 전처리와 후처리는 프롬프트 스킬의 코드 단계로 넣습니다(모델이 실행하는 단계가 하나 이상 있어야 합니다).
  • 함께 내는 자료 — 참고 자료에 value-contract.json 이라는 이름으로 아래 내용을 넣습니다. 없거나 내용이 모자라면 제출되지 않습니다.
{
  "domain": "어떤 업무를 위한 스킬인지 (20자 이상)",
  "differentiator": "AI 에 그냥 물어보는 것과 무엇이 다른지 (20자 이상)",
  "decision_rules": [
    "이 업무에서 전문가가 쓰는 판단 규칙 — 3개 이상, 각각 20자 이상으로 구체적으로",
    "…",
    "…"
  ],
  "failure_modes": [
    "그 규칙이 잘못 적용되기 쉬운 경우(반례) — 3개 이상, 각각 20자 이상으로 구체적으로",
    "…",
    "…"
  ],
  "evaluation_plan": "어떤 입력으로 무엇을 확인하면 이 스킬이 제대로 동작한다고 볼 수 있는지 (20자 이상)"
}
게시 전에 운영자가 확인하는 것통과 기준
실제 실행 시험서로 다른 입력 6개 이상으로 AI 를 실제로 실행합니다. 공개하지 않은 입력과 악의적인 입력이 각각 1개 이상 들어갑니다. 모든 시험에서 필수 조건을 지켜야 하고, 치명적인 오류가 하나도 없어야 합니다.
전문 판단의 품질원문 인용, 조건 보존, 업무별 전문 판단, 바로 쓸 수 있는 결과물인지를 평가합니다. 전문 기준 100점 가운데 85점 이상이어야 합니다. 형식이 맞는 것만으로는 통과하지 않습니다.
일반 지시문과의 비교같은 모델·같은 입력·같은 결과 형식으로 일반 지시문과 비교해 5점 이상 나아야 합니다. 차이가 없으면 단순 호출로 봅니다.
독립 검토운영자가 원문·결과·평가를 직접 검토합니다. 상품 설명의 주장이나 판매자가 보낸 점수, AI 의 자기 평가로는 승인하지 않습니다.
  • 평가는 제출한 그 버전의 내용에 묶입니다. 지시문·입력과 결과 형식·가격을 바꾸면 새 버전으로 다시 평가받습니다.
  • 평가 근거가 확인되지 않은 버전은 승인·게시·판매 재개가 되지 않고, 새 실행도 받지 않습니다.
  • 화면에 보이는 점수·시간·금액은 시험한 입력에서 관측한 값입니다. 모든 입력에서 같은 결과를 보장한다는 뜻이 아닙니다.

쓸모가 없는 스킬은 게시되지 않습니다

형식을 갖췄더라도 구매자에게 줄 가치가 없다고 판단되면 심사에서 탈락할 수 있습니다. 다음과 같은 경우입니다.

  • 입력을 그대로 또는 거의 그대로 돌려주는 것, 인사말·잡담처럼 결과에 쓸모가 없는 것
  • 지시문이 한두 줄뿐이어서 모델에 그냥 물어본 것과 다르지 않은 것, 일반 지시문과 비교해 나은 점이 없는 것
  • AI 를 쓰지 않는 정규식·단순 스크립트, 누구나 바로 할 수 있는 한 줄짜리 계산·변환, 무작위 값만 돌려주는 것
  • 시험용으로 만든 것, 내용이 비어 있거나 예제가 성의 없이 채워진 것
  • 이미 게시된 스킬과 사실상 같은 것(이름·문구만 바꾼 것)
  • 결과가 실행할 때마다 크게 달라 믿고 쓸 수 없는 것, 틀린 결과를 자주 내는 것
  • 설명한 일을 실제로는 하지 못하는 것

심사 결과

결과뜻과 다음 할 일
승인 · 게시판매가 시작됩니다. 판매 화면, REST API, MCP 도구에서 실행됩니다.
보완 요청고치면 게시할 수 있습니다. 판매자 센터에 표시된 심사 의견을 보고 고쳐서 다시 제출합니다.
반려지금 내용으로는 게시할 수 없습니다. 사유가 함께 표시됩니다. 금지 행위에 해당하거나, 전문 AI 스킬 기준에 미치지 못하거나, 쓸모가 없다고 판단된 경우입니다.
  • 심사를 통과했더라도 이후에 기준에 어긋나는 점이 확인되거나 구매자의 피해가 이어질 우려가 있으면 판매를 중지할 수 있습니다. 사유는 판매자 센터에 표시됩니다.
  • 판매가 중지된 스킬은 사유를 바로잡은 뒤 고객지원으로 재개를 요청하거나(재개에도 그 버전의 평가 근거가 필요합니다), 판매자 센터에서 삭제할 수 있습니다. 삭제하면 되돌릴 수 없고, 구매자의 실행 기록과 정산 내역은 그대로 남습니다.
  • 심사는 그 버전의 내용에 대한 것입니다. 호스트·코드·지시문을 바꾸면 새 버전으로 다시 심사를 받습니다.
  • 심사 통과는 결과의 정확성을 에이픽이 보증한다는 뜻이 아닙니다. 결과에 대한 책임은 판매자에게 있습니다.

샘플 모음

'이 샘플로 시작'을 누르면 편집 화면에 그대로 채워집니다. 저장하고 시험 실행한 뒤 내 내용으로 바꿔 보세요.

프롬프트 지시문

유튜브 제목 분석

지시문 하나로 끝나는 가장 기본적인 구성입니다. 평가 기준과 점수 범위를 지시문에 구체적으로 적는 방법을 보여 줍니다.

원본과 형식 보기

지시문

너는 유튜브 제목을 평가하는 편집자다. 입력의 title 을 아래 기준으로 평가한다. topic 이 있으면 그 주제의 시청자를 기준으로 본다.

평가 기준(각 25점)
1. 구체성: 숫자, 고유명사, 상황이 드러나는가
2. 궁금증: 답을 확인하고 싶게 만드는가. 낚시성 과장은 감점한다
3. 대비·변화: 기대와 다른 결과, 전후 비교가 있는가
4. 길이: 공백 포함 18~32자면 만점, 벗어날수록 감점한다

작성 규칙
- score 는 네 기준 점수의 합이다.
- strengths 와 weaknesses 는 각각 1~3개, 한 문장으로 쓴다. 제목에 실제로 있는 표현을 근거로 든다.
- suggestions 는 같은 내용을 유지한 채 고친 제목 3개다. 없는 사실을 지어내지 않는다.

입력 형식

{
  "type": "object",
  "properties": {
    "title": {
      "type": "string",
      "minLength": 2,
      "maxLength": 100,
      "description": "평가할 영상 제목"
    },
    "topic": {
      "type": "string",
      "maxLength": 60,
      "description": "영상 주제"
    }
  },
  "required": [
    "title"
  ]
}

결과 형식

{
  "type": "object",
  "properties": {
    "score": {
      "type": "integer",
      "minimum": 0,
      "maximum": 100,
      "description": "종합 점수"
    },
    "strengths": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 5,
      "description": "잘된 점"
    },
    "weaknesses": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 5,
      "description": "고칠 점"
    },
    "suggestions": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 3,
      "description": "고친 제목"
    }
  },
  "required": [
    "score",
    "strengths",
    "weaknesses",
    "suggestions"
  ]
}

예제

{
  "input": {
    "title": "연봉 1억이면 월급 833만원 받을까?",
    "topic": "경제 숏츠"
  },
  "output": {
    "score": 86,
    "strengths": [
      "구체적인 숫자 두 개가 대비를 만든다",
      "질문형이라 답을 확인하고 싶어진다"
    ],
    "weaknesses": [
      "누구에게 해당하는 이야기인지 드러나지 않는다"
    ],
    "suggestions": [
      "연봉 1억, 통장에 찍히는 돈은 833만원이 아닙니다",
      "연봉 1억 직장인의 실제 월급을 계산해 봤습니다",
      "연봉 1억인데 왜 월 650만원만 들어올까"
    ]
  }
}
프롬프트 지시문참고 자료숨은 예시

상품명 규칙 검사

자주 바뀌는 규칙을 참고 자료 파일로 분리하고, 구매자에게 보이지 않는 예시로 결과의 말투와 형식을 고정하는 방법을 보여 줍니다.

원본과 형식 보기

지시문

너는 오픈마켓 상품명 검수자다. 입력의 product_name 을 참고 자료 '상품명 규칙.md' 의 규칙으로 검사한다.

- 어긴 규칙마다 issues 에 '규칙 이름: 해당 표현' 형식으로 한 줄씩 적는다.
- 어긴 것이 없으면 passed 는 true, issues 는 빈 배열, suggestion 은 원래 상품명 그대로다.
- suggestion 은 어긴 표현만 걷어 낸 상품명이다. 새 단어를 덧붙이지 않는다.

참고 자료 · 상품명 규칙.md 결과에 인용 허용

# 상품명 규칙

## 금지어
최저가, 초특가, 파격, 땡처리, 1위, 최고, 무료배송, 당일발송

## 길이
공백 포함 50자 이하

## 특수문자
같은 특수문자를 연달아 쓰지 않는다(!!, ★★, ~~). 괄호는 한 쌍까지 허용한다.

## 단어 반복
같은 단어를 두 번 이상 쓰지 않는다.

숨은 예시

[
  {
    "input": {
      "product_name": "국산 스테인리스 텀블러 500ml",
      "category": "생활용품"
    },
    "output": {
      "passed": true,
      "issues": [],
      "suggestion": "국산 스테인리스 텀블러 500ml"
    }
  },
  {
    "input": {
      "product_name": "★★초특가★★ 여름 반팔 티셔츠 무료배송",
      "category": "패션"
    },
    "output": {
      "passed": false,
      "issues": [
        "금지어 포함: 초특가",
        "금지어 포함: 무료배송",
        "특수문자 반복: ★★"
      ],
      "suggestion": "여름 반팔 티셔츠"
    }
  }
]

입력 형식

{
  "type": "object",
  "properties": {
    "product_name": {
      "type": "string",
      "minLength": 2,
      "maxLength": 100,
      "description": "검사할 상품명"
    },
    "category": {
      "type": "string",
      "enum": [
        "생활용품",
        "패션",
        "식품",
        "가전"
      ],
      "description": "상품 분류"
    }
  },
  "required": [
    "product_name"
  ]
}

결과 형식

{
  "type": "object",
  "properties": {
    "passed": {
      "type": "boolean",
      "description": "규칙 통과 여부"
    },
    "issues": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 10,
      "description": "어긴 항목"
    },
    "suggestion": {
      "type": "string",
      "maxLength": 100,
      "description": "고친 상품명"
    }
  },
  "required": [
    "passed",
    "issues",
    "suggestion"
  ]
}

예제

{
  "input": {
    "product_name": "최저가!! 튼튼한 접이식 우산 우산",
    "category": "생활용품"
  },
  "output": {
    "passed": false,
    "issues": [
      "금지어 포함: 최저가",
      "특수문자 반복: !!",
      "단어 반복: 우산"
    ],
    "suggestion": "튼튼한 접이식 우산"
  }
}
프롬프트 지시문참고 자료단계 실행

계약서 위험 조항 찾기

한 번에 시키면 놓치기 쉬운 일을 '조항 뽑기 → 위험 판단' 두 단계로 나누는 방법을 보여 줍니다. 판단 기준은 인용을 막은 참고 자료로 둡니다.

원본과 형식 보기

지시문

너는 용역·외주 계약서를 검토하는 실무자다. 입력의 party 입장에서 contract_text 를 검토한다.
계약서에 적힌 내용만 근거로 삼고, 적혀 있지 않은 조항을 지어내지 않는다.
문장은 짧게 쓰고 법률 용어는 풀어서 쓴다.

참고 자료 · 검토 기준.md 인용 막음

# 검토 기준

- 대금: 지급 기한이 검수 후 60일을 넘으면 중간, 기한이 없으면 높음.
- 손해배상: 한도가 없거나 간접 손해까지 포함하면 높음.
- 지식재산권: 기존 보유 자산까지 넘기면 중간, 대가 없이 전부 넘기면 높음.
- 해지: 한쪽만 임의로 해지할 수 있으면 높음, 정산 기준이 없으면 중간.
- 비밀유지: 기간이 무기한이면 낮음, 위약벌이 붙으면 중간.

단계 1 · extract

계약서에서 대금, 손해배상, 지식재산권, 해지, 비밀유지에 해당하는 조항을 모두 찾는다. {"clauses": [{"clause": 조항 번호나 제목, "text": 원문 요지}]} 형식으로 출력한다. 판단은 하지 않는다.

단계 2 · assess

앞 단계에서 찾은 조항마다 참고 자료 '검토 기준.md' 로 위험도를 정하고, 이유와 협의할 내용을 한 문장씩 쓴다. 기준에 걸리지 않는 조항은 결과에 넣지 않는다. summary 는 가장 위험한 조항부터 두세 문장으로 쓴다.

입력 형식

{
  "type": "object",
  "properties": {
    "contract_text": {
      "type": "string",
      "minLength": 100,
      "maxLength": 12000,
      "description": "계약서 본문"
    },
    "party": {
      "type": "string",
      "enum": [
        "수급인",
        "도급인"
      ],
      "default": "수급인",
      "description": "내 입장"
    }
  },
  "required": [
    "contract_text"
  ]
}

결과 형식

{
  "type": "object",
  "properties": {
    "risks": {
      "type": "array",
      "maxItems": 12,
      "description": "위험 조항",
      "items": {
        "type": "object",
        "properties": {
          "clause": {
            "type": "string",
            "description": "조항 번호나 제목"
          },
          "level": {
            "type": "string",
            "enum": [
              "높음",
              "중간",
              "낮음"
            ],
            "description": "위험도"
          },
          "reason": {
            "type": "string",
            "description": "위험한 이유"
          },
          "ask": {
            "type": "string",
            "description": "협의할 내용"
          }
        },
        "required": [
          "clause",
          "level",
          "reason",
          "ask"
        ]
      }
    },
    "summary": {
      "type": "string",
      "maxLength": 400,
      "description": "전체 요약"
    }
  },
  "required": [
    "risks",
    "summary"
  ]
}

예제

{
  "input": {
    "contract_text": "제5조(대금) 도급인은 검수 완료일로부터 90일 이내에 대금을 지급한다. 제8조(손해배상) 수급인은 본 계약과 관련하여 도급인에게 발생한 모든 손해를 배상한다. 제11조(지식재산권) 결과물과 그 과정에서 만든 모든 산출물의 권리는 도급인에게 귀속된다.",
    "party": "수급인"
  },
  "output": {
    "risks": [
      {
        "clause": "제5조(대금)",
        "level": "중간",
        "reason": "지급 기한이 검수 후 90일로 길다.",
        "ask": "검수 완료 후 30일 이내 지급으로 조정"
      },
      {
        "clause": "제8조(손해배상)",
        "level": "높음",
        "reason": "배상 범위와 한도가 정해져 있지 않다.",
        "ask": "배상 한도를 계약금액 이내로, 범위를 직접 손해로 한정"
      },
      {
        "clause": "제11조(지식재산권)",
        "level": "중간",
        "reason": "수급인이 원래 갖고 있던 도구와 라이브러리까지 넘어갈 수 있다.",
        "ask": "기존 보유 자산은 수급인에게 남긴다는 문구 추가"
      }
    ],
    "summary": "손해배상 한도가 없는 점이 가장 큰 위험입니다. 대금 지급 기한과 기존 보유 자산의 권리도 함께 협의하는 것이 좋습니다."
  }
}
프롬프트 지시문입력 전달 형식

리뷰 답글 작성

입력값을 JSON 그대로가 아니라 글 틀에 끼워 넣어 모델에 넘기는 방법을 보여 줍니다. 값의 역할이 분명해져 결과가 안정됩니다.

원본과 형식 보기

지시문

너는 쇼핑몰 고객 응대 담당자다. 리뷰에 달 답글을 쓴다.

- 별점 1~2점: 사과로 시작하고, 리뷰에서 지적한 내용을 한 번 짚은 뒤 개선하겠다는 말로 맺는다.
- 별점 3점: 감사 인사 뒤에 아쉬웠던 점을 짚는다.
- 별점 4~5점: 감사 인사와 리뷰에 나온 구체적인 내용 한 가지를 언급한다.
- 환불, 교환, 보상, 쿠폰을 약속하지 않는다.
- 답글은 3~5문장이다. 가게 이름이 있으면 첫 문장에 넣는다.
- category 는 리뷰가 주로 다룬 내용 하나를 고른다.

입력 전달 형식

가게 이름: {{store_name}}
별점: {{rating}}점
답글 말투: {{tone}}

고객 리뷰:
{{review}}

입력 형식

{
  "type": "object",
  "properties": {
    "review": {
      "type": "string",
      "minLength": 5,
      "maxLength": 1000,
      "description": "고객 리뷰"
    },
    "rating": {
      "type": "integer",
      "minimum": 1,
      "maximum": 5,
      "description": "별점"
    },
    "tone": {
      "type": "string",
      "enum": [
        "정중하게",
        "친근하게"
      ],
      "default": "정중하게",
      "description": "답글 말투"
    },
    "store_name": {
      "type": "string",
      "maxLength": 30,
      "description": "가게 이름"
    }
  },
  "required": [
    "review",
    "rating"
  ]
}

결과 형식

{
  "type": "object",
  "properties": {
    "reply": {
      "type": "string",
      "maxLength": 500,
      "description": "답글"
    },
    "category": {
      "type": "string",
      "enum": [
        "칭찬",
        "배송",
        "품질",
        "응대",
        "기타"
      ],
      "description": "리뷰 분류"
    }
  },
  "required": [
    "reply",
    "category"
  ]
}

예제

{
  "input": {
    "review": "배송이 일주일이나 걸렸어요. 물건은 괜찮은데 다음엔 안 살 것 같아요.",
    "rating": 2,
    "tone": "정중하게",
    "store_name": "푸른상회"
  },
  "output": {
    "reply": "안녕하세요, 푸른상회입니다. 배송이 늦어져 불편을 드려 죄송합니다. 출고 과정을 다시 점검하겠습니다. 상품은 마음에 드셨다니 다행입니다. 다음에는 더 빠르게 받아 보실 수 있도록 하겠습니다.",
    "category": "배송"
  }
}
코드 Python 코드

JSON 키 이름 정리 (Python)

run(input) 함수 하나로 끝나는 코드 상품입니다. 표준 라이브러리만으로 정해진 규칙을 정확하게 처리하는 일에 맞습니다.

원본과 형식 보기

코드

import re


def to_snake(name):
    text = re.sub(r'[\s\-]+', '_', name.strip())
    text = re.sub(r'(.)([A-Z][a-z]+)', r'\1_\2', text)
    text = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', text)
    return re.sub(r'_+', '_', text).lower()


def convert(value, counter):
    if isinstance(value, dict):
        result = {}
        for key, item in value.items():
            new_key = to_snake(key)
            if new_key != key:
                counter[0] += 1
            result[new_key] = convert(item, counter)
        return result
    if isinstance(value, list):
        return [convert(item, counter) for item in value]
    return value


def run(input):
    counter = [0]
    data = convert(input['data'], counter)
    return {'data': data, 'renamed': counter[0]}

입력 형식

{
  "type": "object",
  "properties": {
    "data": {
      "type": "object",
      "additionalProperties": true,
      "description": "정리할 JSON 객체"
    }
  },
  "required": [
    "data"
  ]
}

결과 형식

{
  "type": "object",
  "properties": {
    "data": {
      "type": "object",
      "additionalProperties": true,
      "description": "정리한 객체"
    },
    "renamed": {
      "type": "integer",
      "minimum": 0,
      "description": "바꾼 키 개수"
    }
  },
  "required": [
    "data",
    "renamed"
  ]
}

예제

{
  "input": {
    "data": {
      "userName": "kim",
      "OrderList": [
        {
          "item-id": 1
        }
      ]
    }
  },
  "output": {
    "data": {
      "user_name": "kim",
      "order_list": [
        {
          "item_id": 1
        }
      ]
    },
    "renamed": 3
  }
}
코드 Node.js 코드

글 통계 계산 (Node.js)

Node.js 로 쓰는 코드 상품입니다. exports.run 또는 function run 으로 시작점을 정의합니다.

원본과 형식 보기

코드

exports.run = function (input) {
    const text = String(input.text);
    const top = input.top || 5;
    const words = text.split(/\s+/).map(function (word) { return word.replace(/[.,!?"'()]/g, ''); }).filter(Boolean);
    const counts = new Map();
    words.forEach(function (word) { counts.set(word, (counts.get(word) || 0) + 1); });
    const topWords = Array.from(counts.entries())
        .sort(function (a, b) { return b[1] - a[1]; })
        .slice(0, top)
        .map(function (entry) { return { word: entry[0], count: entry[1] }; });
    return {
        chars: Array.from(text).length,
        chars_no_space: Array.from(text.replace(/\s/g, '')).length,
        sentences: text.split(/[.!?]+/).filter(function (part) { return part.trim(); }).length,
        top_words: topWords,
    };
};

입력 형식

{
  "type": "object",
  "properties": {
    "text": {
      "type": "string",
      "minLength": 1,
      "maxLength": 20000,
      "description": "분석할 글"
    },
    "top": {
      "type": "integer",
      "minimum": 1,
      "maximum": 20,
      "default": 5,
      "description": "자주 쓴 단어 개수"
    }
  },
  "required": [
    "text"
  ]
}

결과 형식

{
  "type": "object",
  "properties": {
    "chars": {
      "type": "integer",
      "description": "글자 수(공백 포함)"
    },
    "chars_no_space": {
      "type": "integer",
      "description": "글자 수(공백 제외)"
    },
    "sentences": {
      "type": "integer",
      "description": "문장 수"
    },
    "top_words": {
      "type": "array",
      "description": "자주 쓴 단어",
      "items": {
        "type": "object",
        "properties": {
          "word": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          }
        },
        "required": [
          "word",
          "count"
        ]
      }
    }
  },
  "required": [
    "chars",
    "chars_no_space",
    "sentences",
    "top_words"
  ]
}

예제

{
  "input": {
    "text": "배송이 빨라요. 포장도 꼼꼼해요. 배송이 정말 빨라서 놀랐어요.",
    "top": 2
  },
  "output": {
    "chars": 35,
    "chars_no_space": 28,
    "sentences": 3,
    "top_words": [
      {
        "word": "배송이",
        "count": 2
      },
      {
        "word": "빨라요",
        "count": 1
      }
    ]
  }
}
코드 Python 코드실행 파일 여러 개

주문 합계 계산 (Python, 여러 파일)

시작 파일(main.py) 말고도 모듈과 자료 파일을 함께 올리는 구성입니다. 올린 파일은 격리된 실행 환경의 작업 폴더에 경로 그대로 풀리므로 import 와 상대 경로로 읽습니다.

원본과 형식 보기

코드

import json

from rules.tax import tax_for


def load_rates():
    # 함께 올린 자료 파일은 작업 폴더 기준 상대 경로로 읽는다.
    with open('data/rates.json', encoding='utf-8') as handle:
        return json.load(handle)


def run(input):
    rates = load_rates()
    lines = []
    subtotal = 0
    for item in input['items']:
        amount = int(item['price']) * int(item['quantity'])
        subtotal += amount
        lines.append({'name': item['name'], 'amount': amount})
    tax = tax_for(subtotal, rates.get(input.get('region', 'KR'), 0))
    return {'lines': lines, 'subtotal': subtotal, 'tax': tax, 'total': subtotal + tax}

실행 파일 · rules/__init__.py

실행 파일 · rules/tax.py

def tax_for(amount, rate_percent):
    # 원 단위 미만은 버린다.
    return amount * rate_percent // 100

실행 파일 · data/rates.json

{"KR": 10, "JP": 10, "US": 0}

입력 형식

{
  "type": "object",
  "properties": {
    "items": {
      "type": "array",
      "minItems": 1,
      "maxItems": 100,
      "description": "주문 항목",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 60
          },
          "price": {
            "type": "integer",
            "minimum": 0
          },
          "quantity": {
            "type": "integer",
            "minimum": 1
          }
        },
        "required": [
          "name",
          "price",
          "quantity"
        ]
      }
    },
    "region": {
      "type": "string",
      "enum": [
        "KR",
        "JP",
        "US"
      ],
      "default": "KR",
      "description": "세율을 적용할 지역"
    }
  },
  "required": [
    "items"
  ]
}

결과 형식

{
  "type": "object",
  "properties": {
    "lines": {
      "type": "array",
      "description": "항목별 금액",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "amount": {
            "type": "integer"
          }
        },
        "required": [
          "name",
          "amount"
        ]
      }
    },
    "subtotal": {
      "type": "integer",
      "description": "세금 전 합계"
    },
    "tax": {
      "type": "integer",
      "description": "세금"
    },
    "total": {
      "type": "integer",
      "description": "최종 합계"
    }
  },
  "required": [
    "lines",
    "subtotal",
    "tax",
    "total"
  ]
}

예제

{
  "input": {
    "items": [
      {
        "name": "키보드",
        "price": 45000,
        "quantity": 2
      },
      {
        "name": "마우스",
        "price": 20000,
        "quantity": 1
      }
    ],
    "region": "KR"
  },
  "output": {
    "lines": [
      {
        "name": "키보드",
        "amount": 90000
      },
      {
        "name": "마우스",
        "amount": 20000
      }
    ],
    "subtotal": 110000,
    "tax": 11000,
    "total": 121000
  }
}
코드 셸 스크립트실행 파일 여러 개

CSV 열 통계 (셸 스크립트)

셸 스크립트로 쓰는 코드 상품입니다. 입력(JSON)은 표준 입력과 $SKILL_INPUT 파일로 들어오고, 표준 출력에 쓴 JSON 객체 하나가 결과입니다. 스크립트에서 함께 올린 Python·awk 파일을 부를 수 있습니다.

원본과 형식 보기

코드

#!/bin/bash
# 입력(JSON)은 표준 입력과 $SKILL_INPUT 파일로 들어온다. 결과는 표준 출력에 JSON 객체 하나로 쓴다.
# 진행 기록을 남기려면 표준 오류(>&2)로 쓴다.
set -euo pipefail

python3 scripts/pick.py "$SKILL_INPUT" > /tmp/values.txt
awk -f scripts/stats.awk /tmp/values.txt

실행 파일 · scripts/pick.py

import csv, io, json, sys

payload = json.load(open(sys.argv[1], encoding='utf-8'))
rows = csv.DictReader(io.StringIO(payload['csv']))
for row in rows:
    cell = (row.get(payload['column']) or '').replace(',', '').strip()
    try:
        print(float(cell))
    except ValueError:
        continue

실행 파일 · scripts/stats.awk

{ value = $1 + 0; sum += value; if (NR == 1 || value > max) max = value; if (NR == 1 || value < min) min = value }
END {
    if (NR == 0) { print "{\"count\": 0, \"sum\": 0, \"average\": 0, \"max\": 0, \"min\": 0}"; exit }
    printf "{\"count\": %d, \"sum\": %s, \"average\": %s, \"max\": %s, \"min\": %s}", NR, sum, sum / NR, max, min
}

입력 형식

{
  "type": "object",
  "properties": {
    "csv": {
      "type": "string",
      "minLength": 3,
      "maxLength": 50000,
      "description": "CSV 글(첫 줄은 열 이름)"
    },
    "column": {
      "type": "string",
      "minLength": 1,
      "maxLength": 60,
      "description": "계산할 열 이름"
    }
  },
  "required": [
    "csv",
    "column"
  ]
}

결과 형식

{
  "type": "object",
  "properties": {
    "count": {
      "type": "integer",
      "description": "숫자 칸 개수"
    },
    "sum": {
      "type": "number",
      "description": "합계"
    },
    "average": {
      "type": "number",
      "description": "평균"
    },
    "max": {
      "type": "number",
      "description": "최댓값"
    },
    "min": {
      "type": "number",
      "description": "최솟값"
    }
  },
  "required": [
    "count",
    "sum",
    "average",
    "max",
    "min"
  ]
}

예제

{
  "input": {
    "csv": "상품,금액\n키보드,\"12,000\"\n마우스,8000\n패드,10000\n합계,없음",
    "column": "금액"
  },
  "output": {
    "count": 3,
    "sum": 30000,
    "average": 10000,
    "max": 12000,
    "min": 8000
  }
}
프롬프트 지시문단계 실행코드 단계실행 파일 여러 개

리뷰 정리 (모델 + 코드 단계)

모델이 하는 일과 코드가 하는 일을 단계로 섞는 구성입니다. 세는 일·중복 제거·형식 맞추기는 코드 단계가 정확하게 하고, 읽고 요약하는 일만 모델에 맡깁니다. 코드 단계는 { input, steps } 를 받아 객체를 돌려줍니다.

원본과 형식 보기

지시문

너는 쇼핑몰 리뷰를 정리하는 분석가다.
리뷰에 실제로 있는 내용만 근거로 삼고, 과장하거나 없는 내용을 지어내지 않는다.
한국어 평서문으로 짧게 쓴다.

단계 1 · collect 코드 실행 (Python)

실행 파일: scripts/collect.py

단계 2 · write

앞 단계 collect 의 reviews 만 근거로 summary(두 문장 이내), strengths(장점, 최대 3개), complaints(불만, 최대 3개)를 JSON 객체로 쓴다. 해당하는 내용이 없으면 빈 배열로 둔다.

단계 3 · finish 코드 실행 (Python)

실행 파일: scripts/finish.py

실행 파일 · scripts/collect.py

# 코드 단계는 { 'input': 구매자 입력, 'steps': { 앞 단계 이름: 그 결과 } } 를 받는다.
def run(payload):
    reviews = payload['input']['reviews']
    seen = []
    for text in reviews:
        cleaned = ' '.join(str(text).split())
        if cleaned and cleaned not in seen:
            seen.append(cleaned)
    return {'total': len(reviews), 'unique': len(seen), 'reviews': seen[:30]}

실행 파일 · scripts/finish.py

# 마지막 단계가 돌려준 객체가 상품의 결과다. 결과 형식에 맞춰 정리한다.
def as_list(value, limit):
    if not isinstance(value, list):
        return []
    return [str(item) for item in value if str(item).strip()][:limit]


def run(payload):
    counted = payload['steps']['collect']
    written = payload['steps']['write']
    return {
        'total': counted['total'],
        'unique': counted['unique'],
        'summary': str(written.get('summary', ''))[:300],
        'strengths': as_list(written.get('strengths'), 3),
        'complaints': as_list(written.get('complaints'), 3),
    }

입력 형식

{
  "type": "object",
  "properties": {
    "reviews": {
      "type": "array",
      "minItems": 1,
      "maxItems": 50,
      "items": {
        "type": "string",
        "maxLength": 300
      },
      "description": "리뷰 목록"
    },
    "product": {
      "type": "string",
      "maxLength": 60,
      "description": "상품 이름"
    }
  },
  "required": [
    "reviews"
  ]
}

결과 형식

{
  "type": "object",
  "properties": {
    "total": {
      "type": "integer",
      "description": "받은 리뷰 수"
    },
    "unique": {
      "type": "integer",
      "description": "중복을 뺀 리뷰 수"
    },
    "summary": {
      "type": "string",
      "description": "한 줄 요약"
    },
    "strengths": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 3,
      "description": "장점"
    },
    "complaints": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "maxItems": 3,
      "description": "불만"
    }
  },
  "required": [
    "total",
    "unique",
    "summary",
    "strengths",
    "complaints"
  ]
}

예제

{
  "input": {
    "reviews": [
      "배송이 빨라요",
      "배송이  빨라요",
      "포장이 꼼꼼해요",
      "소음이 조금 있어요"
    ],
    "product": "미니 선풍기"
  },
  "output": {
    "total": 4,
    "unique": 3,
    "summary": "배송과 포장은 만족스럽지만 소음을 아쉬워하는 의견이 있습니다.",
    "strengths": [
      "배송이 빠르다",
      "포장이 꼼꼼하다"
    ],
    "complaints": [
      "소음이 조금 있다"
    ]
  }
}