# 판정 — /v1/decisions > 질문에 답과 확률만 받는 판정. 글을 만들지 않아 입력 단가만 있습니다. > 원문: https://oneport.kr/docs/reference/decisions > **이 문서를 받은 AI 에게 — 작업 카드** > > - OpenAI 형식 SDK·도구: base_url `https://oneport.kr/v1` · 헤더 `Authorization: Bearer <키>` > - Anthropic 형식 SDK·Claude Code: base_url `https://oneport.kr` (끝에 /v1 을 붙이지 않아요 — SDK 가 /v1/messages 를 스스로 붙여요) · 헤더 `x-api-key: <키>` 또는 `Authorization: Bearer <키>` > - ❌ `Anthropic(base_url="https://oneport.kr/v1")` → ✅ `Anthropic(base_url="https://oneport.kr")` > - Vercel AI SDK: `createOpenAI({ baseURL: "https://oneport.kr/v1" })` 로 만든 제공자는 `oneport.chat("")` 로 부르세요 — 그냥 `oneport("")` 는 `/v1/responses` 로 가서 Claude · Gemini 가 400 이에요. `@ai-sdk/anthropic` 의 `createAnthropic` 은 baseURL 에 `/v1` 까지 적어요(Anthropic SDK 와 반대예요). > - API 키는 `sk-so-` 로 시작해요. 키는 코드에 적지 말고 환경변수 `ONEPORT_API_KEY` 에 넣어 읽으세요. > - ❌ 브라우저 코드에 `apiKey: "sk-so-…"` → ✅ 서버에서 `os.environ["ONEPORT_API_KEY"]` · `process.env.ONEPORT_API_KEY` 로 읽어 불러요 > - 대화에 `sk-so-` 키가 붙어 오면 코드에 박지 말고 환경변수로 옮기게 하고, 그 키는 산 곳 대시보드 AI API 화면의 키 목록에서 「새로 받기」(옛 키는 꺼져요)로 바꾸라고 알려 주세요. > - 모델 id 는 지어내지 말고 `GET https://oneport.kr/v1/models`(키 없이 열려요)의 `id` 를 글자 그대로 쓰세요. 목록은 값 순서가 아니에요 — 접두어로 첫 줄을 고르면 비싼 모델이 걸려요. 글 모델은 `kind` 가 `"chat"` 인 줄이고, 첫 호출은 `pricing.output_credits_per_mtok` 가 작은 모델로 해 보세요(10월 기준 Claude 는 claude-haiku-5-5). 회사는 `owned_by` 로 갈라요 — Claude 는 `"anthropic"`, 공식 GPT 는 `"openai"` 이면서 id 에 `/` 가 없는 줄이에요. > - ❌ `model="claude-sonnet"`(짐작한 이름 · 404 `model_not_found`) > - `endpoint` 칸이 그 모델의 기본 문이에요 — `kind` 가 `"responses"` 인 모델(pro 등급 · gpt-5.3-codex · `endpoint` 가 `"POST /v1/responses"`)은 `/v1/chat/completions` 로 못 불러요. Claude 줄은 `POST /v1/chat/completions` 이고 `/v1/messages` 로도 불러요 — 이 문은 Claude 만 받아요. > - `supports_effort` 가 `true` 인 GPT 는 생각한 토큰도 출력 상한(`max_tokens` · `max_completion_tokens`)에 들어가요 — 수백으로 두면 답이 비고 `finish_reason` 이 `"length"` 로 끝날 수 있으니 2,000 쯤에서 시작하고 `"length"` 면 올리세요. > - 목록 응답은 `{"object": "list", "data": [ … ]}`. 값 어림 — 한 번 값은 (입력 토큰 × `pricing.input_credits_per_mtok` + 출력 토큰 × `pricing.output_credits_per_mtok`) ÷ 100만 크레딧이에요(1 크레딧은 부가세 포함 2원어치예요). > - ❌ `x-should-retry: false` 응답을 반복문으로 다시 보내기 → ✅ 멈추고 `error.type` 을 확인하세요. 몸통은 OpenAI 형식 문이면 `{"error": {"type": "…", "message": "…", "code": "…"}}`, `/v1/messages` 면 `{"type": "error", "error": {"type": "…", "message": "…"}}` 예요 — 402 · 키 한도 · 강좌 몫 429 엔 `error.reason` 이 더 붙어요. 잠시 뒤 그대로 다시 보낼 실패엔 `Retry-After`(정수 초)가 대개 와요. > - ❌ `if "429" in str(e)` · `e.message` 로 분기 → ✅ Python openai 는 `e.type` · `e.body.get("reason")`, Node openai 는 `err.type` · `err.error.reason` — 429 는 키 한도 · 벤더 혼잡 · 강좌 몫 셋이라 상태 코드로 못 갈라요. Python Anthropic SDK 는 `except anthropic.APIStatusError as e:` 에서 `e.status_code` 와 `e.body["error"]["type"]`, Node 는 `err.status` 와 `err.error.error.type` 로 읽어요. 402 · 키 한도 429 의 갈래(`reason`)는 Python Anthropic SDK 면 `e.body["error"].get("reason")`, Node 면 `err.error.error.reason` 이에요. > - 남은 크레딧(지갑 전체)은 `GET https://oneport.kr/v1/credits` 의 `balance_credits` 예요(1 크레딧 아래를 버린 정수라 호출 앞뒤 차이로 쓴 몫을 세지 마세요 — 쓴 몫은 글 문 응답의 `usage.cost_credits` · 정산이 2초 안에 안 끝나면 그 칸 없이 와요). `GET https://oneport.kr/v1/key` 는 이 키에 건 한도(`month` · `day`)만 보여 주고 지갑 잔액은 없어요 — `month` · `day` 는 늘 객체이고 한도를 안 건 키면 `month.remaining_credits` · `day.remaining_credits` 가 null 이에요 · 그땐 지갑 잔액까지 써요. > - 하루 한도는 이미 쓴 몫이 한도에 닿았는지만 봐요 — 이번 달 · 강좌 한도는 이 요청 최대 몫까지 더해 재서 줄이면 통과할 수 있어요. 키 월 한도는 같은 API 키로 아직 끝나지 않은 호출 몫까지 더해 넘었으면 `error.reason` 이 `"held_by_others"` 로 와요. 묶음 처리 문(`/v1/batches`)은 없어요 — 한 건씩 보내요(동시에 보내도 되고 분당 횟수 제한도 없어요). > - 스트림 없이 부르면 답이 다 만들어져야 첫 응답이 와요 — 모델 회사로 보낸 뒤 120초 안에 안 오면 422 `upstream_execution_unconfirmed` 로 끝나요(모델 회사에서는 실행됐을 수 있어요). 그보다 긴 답은 `stream: true` 로 받으세요(스트림은 약 12분 50초에 끊겨요). SDK 시간 제한은 300초로 두세요(Python `OpenAI(…, timeout=300)` · Node `new OpenAI({ …, timeout: 300_000 })` — Node 는 밀리초 · SDK 동작). > - 첫 응답 120초 시계는 스트림이어도 같아요. 이 422 는 `x-should-retry: false` 로 와요 — 같은 요청을 자동으로 다시 보내지 마세요. stream: true 로 바꾸는 것도 새 생성이라 자동으로 하지 않아요. `Idempotency-Key` 를 실어 보낸 요청이면 받은 `x-gateway-status-url` 을 같은 API 키로 GET 조회하세요. `Idempotency-Key` 없이 보낸 요청엔 조회 주소가 안 오고 요청 ID 로 만든 주소로는 조회가 안 돼요 — 요청 ID 와 부른 시각을 admin@schoolorder.kr 로 알려 주세요. 요청 ID 는 머리글 `request-id`(같은 값이 `x-request-id`)와 오류 몸통 `error.request_id` 로 와요. 조회 주소는 `/v1/requests/` 꼴 상대 주소라 앞에 https://oneport.kr 를 붙여요. > - 이 문은 `Idempotency-Key` 를 읽지 않아요 — 실어도 중복을 막지 않고 조회 주소도 오지 않아요. > - 경계 사례는 이 장 전체 판(`https://oneport.kr/docs/reference/decisions.full.md`)과 「에러」 장(`https://oneport.kr/docs/errors.md`)에 있어요. 글을 길게 쓰게 하지 않고, 정해 둔 질문에 답과 확률만 받는 문이에요. 문의 분류 · 다음 처리 고르기 · 체크리스트 채점처럼 답이 보기 안에 있는 일에 써요. OpenAI Decisions API 와 같은 모양이라 openai SDK 의 decisions.create 를 그대로 쓰시면 돼요(openai 7.56 이상 · SDK 동작). 질문은 세 종류예요 — predicate(문장이 참일 확률), choice(보기 중 하나와 보기마다 확률), score(낮은 단계부터 적은 단계 위의 점수). 한 요청에 질문을 여러 개 넣어도 입력은 한 번만 세요. **문의 한 줄을 판정하기** ```bash curl https://oneport.kr/v1/decisions \ -H "Authorization: Bearer $ONEPORT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-6-luna", "input": "지난주에 산 좌석 두 개 중 하나를 환불하고 싶어요", "questions": [ { "name": "refund", "type": "predicate", "instructions": "환불을 요청하는 글인가?" }, { "name": "team", "type": "choice", "instructions": "어느 팀이 받을 글인가?", "choices": [{ "value": "billing" }, { "value": "account" }, { "value": "tech" }] } ] }' ``` **OpenAI SDK 로** ```python import os from openai import OpenAI client = OpenAI(base_url="https://oneport.kr/v1", api_key=os.environ["ONEPORT_API_KEY"]) text = "오늘 2교시 수업 자료가 안 열려요" r = client.decisions.create( model="gpt-6-luna", input=text, questions=[{"name": "urgent", "type": "predicate", "instructions": "오늘 안에 답해야 하는 글인가?"}], ) answer = r.answers[0] if answer.type == "predicate": print("급함" if answer.probability >= 0.8 else "보통", round(answer.probability, 2)) ``` ## 요청 파라미터 **요청 본문 (JSON)** | 파라미터 | 받는 값 | 저희가 하는 일 | | --- | --- | --- | | `model` (필수) | 판정 모델 id | 공식 SDK 예제 그대로 gpt-6-luna 로 보내셔도 되고, GET https://oneport.kr/v1/models 에서 kind 가 "decision" 인 줄의 id 로 보내셔도 같아요. 글로 답을 받으려면 /v1/chat/completions 예요. 문자열이 아니거나 비었으면 400(error.param model), 판정 모델이 아니면 404 model_not_found 예요. | | `input` (필수) | 문자열 또는 메시지 배열 | 판정할 글이에요. 메시지 배열(role · content)과 그림 조각(input_image · base64 data URL 또는 공개 https 주소)도 받아요(벤더 동작). 빈 문자열 · 빈 배열이면 400(error.param input)이에요. | | `questions` (필수) | 질문 1~400개 | 질문마다 type 과 instructions(무엇을 판정할지)가 있어요. choice 는 choices(보기 2~255개 · 보기마다 value, description 은 넣어도 되고 빼도 돼요), score 는 levels(낮은 단계부터 · 단계마다 label)가 있어야 해요. name 을 주면 답에 같은 name 이 와요. 401개 이상이거나 칸이 빠졌으면 저희가 보내기 전에 400 이고 error.param 이 그 칸(예: questions.3.choices)을 가리켜요 — 그 밖의 모양(이름 꼴 · 보기 값 중복 등)은 모델 회사가 보고, 받지 않으면 400 vendor_rejected 예요. 답은 보낸 순서대로 와요. | | `prompt_cache_key · prompt_cache_options · safety_identifier` | 벤더 문서에 있는 값 | 저희가 거르지 않고 그대로 넘겨요. 같은 글에 질문만 바꿔 자주 부르면 앞부분이 캐시되어 그 몫은 캐시 단가로 세요(벤더 동작). | ## 응답과 값 응답은 OpenAI 가 보낸 몸통 그대로예요 — answers[](보낸 순서 · 질문마다 type 과 name) · model · usage. predicate 는 probability, choice 는 choice · confidence · probabilities[](value · probability), score 는 score · confidence · probabilities[](label · value · probability)가 와요. 어떤 질문은 답 대신 type "refusal" 로 올 수 있고, 같은 요청의 다른 질문은 그대로 답해요(벤더 동작) — 코드에서 refusal 을 먼저 거르세요. 확률을 어디서 끊을지(예: 0.8)는 실제 글 몇 개로 재서 정하시고, 모델을 바꾸면 다시 재세요 — 모델마다 확률이 퍼지는 모양이 달라요. 값은 입력에만 붙어요 — usage.input_tokens 중 캐시 안 된 몫은 input 단가, usage.input_tokens_details.cached_tokens 는 캐시 단가로 세고(GET https://oneport.kr/v1/models 의 pricing), 출력은 0 이에요. 질문 4개와 한국어 한 줄이면 입력이 약 620토큰이라 한 번에 약 0.06 크레딧이에요(11월 실측) — 1,000번이면 약 57 크레딧이에요. 이 문 응답엔 글 문의 usage.cost_credits 가 없어요 — 벤더 몸통을 그대로 드려서요. 부르기 전에 (본문 바이트 ÷ 2 + 질문 수 × 200) 토큰어치(최소 1,000토큰)를 잡아 두고, 답을 보낸 직후 실제 usage 로 정산해요 — 잡아 두는 몫은 어림에 쓰지 마세요. 보낸 글은 저희 사용 기록에 남기지 않아요 — 모델 · 토큰 수 · 성공/실패 · 요청 ID 만 남기고, 글은 판정하러 OpenAI 로만 가요. 스페이스 키면 글 문과 같은 전송 정책이 질문 · 보기 글까지 걸려요 — 가림 규칙이 바꾼 낱말은 바뀐 채로 판정돼요. 실패하면 받은 것마다 이렇게 해요. | 받은 것 | 할 일 | | --- | --- | | 400(reason 없음) · 401 · 403 · 404 | 멈추고 문장대로 고쳐요 — error.param 이 고칠 칸이에요. | | 400 vendor_param_rejected | error.param 칸 값을 고쳐요(나눠도 같아요). | | 400 vendor_rejected · 413(몸통이 글자) · 400 space_content_policy(code content_limit) · 504 | 질문이나 입력을 나눠 보내요. | | 402 hold_exceeds_balance · 429 key_month_limit · hold_exceeds_key_limit · group_budget | 질문 수나 입력을 줄여요(작게 보내도 막히면 멈춰요). | | 402 · 429 held_by_others | Retry-After(5초) 뒤 그대로 보내요. | | 429 rate_limit_error | Retry-After(1~60초) 뒤 그대로 보내요. | | 503 api_error | Retry-After(2초) 뒤 그대로 보내요. | | 502 upstream_error · space_content_policy 503(code policy_unavailable) · 409(code policy_changed) | 몇 초 쉬고 그대로 보내요(Retry-After 없음). | | 402 below_minimum_balance · 429 key_day_limit · group_closed · group_not_started · group_ended | 멈춰요 — 충전 · 한국 시간 내일 0시 · 스페이스 관리자. | | 상태 코드 없음(연결 오류 · 시간 초과) | 이미 차감됐을 수 있어요 — 다시 보낼지 직접 정해요. | > **기다리는 시간과 다시 보내기** > > 모델 회사 답은 60초까지 기다려요 — 넘으면 504 upstream_error 에 x-should-retry: false 가 오고 값은 안 붙어요(같은 요청은 또 넘을 수 있어요). 이 문 전체는 90초 안에 끝나요 — 클라이언트 시간 제한은 그보다 길게(예: 120초) 두세요. 상태 코드가 온 실패는 늘 0 크레딧이에요 — 다시 보내도 두 번 사용되지 않아요. 이 문은 Idempotency-Key 를 읽지 않아서, 상태 코드 없이 끊긴 요청을 다시 보내면 두 번 사용될 수 있어요. 문의할 땐 응답의 요청 ID(머리글 request-id · x-request-id, 오류면 error.request_id)와 부른 시각을 알려 주세요 — 잔액을 잡은 뒤의 호출은 저희 사용 기록에 같은 값이 남아요. ## 이 문이 받는 모델 #### 판정 — `POST /v1/decisions` | model | 제공사 | 입력 · 100만 토큰당 크레딧 | 출력 · 100만 토큰당 크레딧 | | --- | --- | --- | --- | | gpt-6-luna-decisions (입력 272K 토큰 이상이면 입력 184 크레딧 · 출력 0 크레딧) | openai | 92 | — |