Ollama System One으로 확률 기반 티켓 분류와 모델 라우팅 만들기

System One의 choice·noul·score 응답을 구분하고, 확률과 confidence의 의미에 맞춰 로컬 티켓 분류와 모델 라우팅 정책을 구성한다.

2026-10-06

분류 결과를 문장에서 꺼내지 말고 응답 필드로 받는다

티켓 분류에 필요한 결과가 billing, bug, account 중 하나라면, 생성된 문장에서 라벨을 찾아내는 대신 선택된 키와 선택지별 확률을 직접 받을 수 있다. Ollama System One은 텍스트 대신 선택·확률·점수를 반환하는 결정 모델 API다. Ollama v0.35.0 릴리스노트는 티켓 분류, 모델 라우팅, 콘텐츠 분류를 사용 사례로 제시한다.

이 글의 기준은 Ollama v0.35.0 (2026-09-28), 모델 nimble / tev1, 엔드포인트 POST /v1/systemone이다. 제공 모델은 Bespoke Labs의 Nimble과 Together AI의 Tev1이며, 아래 요청 예시는 공식 문서의 nimble을 사용한다.

실무에서 바꿔야 할 부분은 모델의 답을 해석하는 경계다. System One은 분류에 필요한 수치를 반환하고, 애플리케이션은 그 수치에 정책을 적용한다. 예를 들어 자동 라우팅 조건을 충족한 티켓은 해당 경로로 보내고, 조건을 충족하지 못한 티켓은 검토 대상으로 남기는 방식이다. 임계값 자체는 API가 정해 주는 값이 아니라 운영자가 검증해 정할 정책이다.

다음 그림은 분류 응답을 받은 뒤 자동 라우팅 여부를 애플리케이션이 결정하는 경계를 보여 준다.

⤢✕충족미충족티켓System One선택된 라벨과 선택지별 확률자동 라우팅 조건 충족해당 경로검토 대상

이 구조에서는 응답을 받았다는 사실과 자동 처리해도 된다는 판단을 분리해야 한다. 특히 confidence를 정답 보장으로 읽으면 이 경계가 무너진다.

질문 타입을 업무의 판단 단위에 맞춘다

공식 Decision 가이드는 분류, 예·아니오 확률, 순서가 있는 기준의 점수화를 지원한다. 질문 타입은 원하는 출력 형태에 맞춰 고른다.

타입 기준 작성 방식 반환 결과 티켓·라우팅에서의 적용
choice 설명이 있는 이름 붙은 선택지 2~26개 가장 가능성이 높은 선택지와 선택지별 확률, confidence 담당 라벨이나 라우팅 후보 선택
noul "false"와 "true"의 설명 true의 확률인 noul, 범위 0~1 환불 요청 여부 같은 조건 판단
score 낮은 수준부터 높은 수준까지 정렬한 설명 2~26개 기준 인덱스의 확률 가중 평균, legend, 확률, confidence 긴급도처럼 순서가 있는 기준 평가

choice는 서로 다른 후보 중 하나를 고를 때 쓴다. 티켓의 담당 분야를 분류한다면 선택지 키를 애플리케이션이 사용할 라벨로 정하고, 설명에는 각 라벨이 맡는 범위를 적는다. 모델 라우팅에도 같은 형태를 적용할 수 있다. 라우팅 후보를 키로 표현하고 후보별 처리 범위를 설명한 뒤, 반환된 키를 애플리케이션의 경로와 연결하는 방식이다.

noul은 “고객이 환불을 요청했는가”처럼 조건 하나의 확률이 필요할 때 맞는다. 응답은 Boolean이 아니라 숫자다. 이를 참·거짓으로 쓰려면 애플리케이션에서 임계값을 적용해야 한다. 기준 설명을 생략하면 "false"와 "true"에는 각각 No, Yes가 사용된다.

score는 후보 사이에 순서가 있을 때 쓴다. 배열의 첫 기준이 인덱스 0이며 마지막 기준이 가장 높은 수준이다. 따라서 기준의 문구뿐 아니라 배열 순서도 점수의 의미를 결정한다. 순서가 없는 담당 부서를 점수 배열에 넣는 대신, 그런 업무에는 choice를 사용하는 편이 목적에 맞는다.

로컬 요청으로 라벨과 분포를 확인한다

공식 가이드는 Ollama v0.35.0 이상을 시작한 뒤 Nimble을 내려받도록 안내한다. 로컬 요청에는 API 키가 필요 없다.

ollama pull nimble

입력은 state, 판단할 질문은 questions, 가능한 답과 설명은 criteria에 넣는다. 아래는 공식 문서에 있는 티켓 분류 요청이다.

curl http://localhost:11434/v1/systemone \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "nimble",
    "state": "Our checkout has returned 500 errors since 9am.",
    "questions": {
      "label": {
        "type": "choice",
        "instructions": "Which label fits this ticket?",
        "criteria": {
          "billing": "Payments and refunds",
          "bug": "Software errors",
          "account": "Login and account access"
        }
      }
    }
  }'

공식 문서의 응답 예시는 다음과 같다. 실제 결과와 사용량은 모델 및 서버 구성에 따라 달라질 수 있다.

{
  "model": "nimble",
  "answers": {
    "label": {
      "type": "choice",
      "choice": "bug",
      "probabilities": {"billing": 0.0125, "bug": 0.9781, "account": 0.0093},
      "confidence": 0.8906
    }
  },
  "usage": {"input_tokens": 174, "output_tokens": 1}
}

애플리케이션은 answers.label.choice에서 선택된 라벨을 읽고, probabilities에서 후보별 분포를 확인할 수 있다. 이 예시에서는 bug가 선택되었으며 그 확률은 0.9781이다.

확률은 요청에 제공한 후보 사이에서 정규화된 값이다. 따라서 후보 목록을 설계하는 일도 분류 정책의 일부다. 업무에 필요한 범위가 후보에서 빠져 있어도 응답은 제공된 후보 사이의 확률을 반환한다. 선택된 키만 저장하기보다 후보 목록과 설명도 함께 관리해야 결과를 해석할 기준이 남는다.

스키마상 choice의 설명을 null로 두면 키 자체가 설명으로 사용된다. 확률이 동률이면 요청 순서에서 먼저 나온 선택지가 선택된다. 설명을 생략하거나 후보 순서를 바꾸는 작업도 분류 설정의 변경으로 취급하는 편이 좋다.

확률·confidence·score를 서로 다른 값으로 읽는다

OpenAPI 스키마는 confidence를 다음과 같이 정의한다.

confidence = 1 - H(p) / ln(N)

H(p)는 확률 분포의 엔트로피이고 N은 후보 수다. 확률이 균등하면 confidence는 0이며, 한 후보가 지배하면 1에 가까워진다. 이는 분포가 얼마나 한쪽에 모였는지를 나타낸다. 정답 여부에 맞춰 보정된 값은 아니다.

앞선 예시의 bug 확률 0.9781과 confidence 0.8906은 같은 수치를 다른 이름으로 반환한 것이 아니다. 전자는 특정 후보에 배정된 확률이고, 후자는 전체 분포의 집중도다. 자동 처리 기준을 설계할 때는 어떤 값을 어떤 목적으로 쓰는지 먼저 정해야 한다.

score도 확률이나 confidence와 구분해야 한다. 공식 긴급도 예시의 기준은 Routine, Soon, Immediate 순서이며 점수는 다음 식으로 계산한다.

score = 0 × P(Routine) + 1 × P(Soon) + 2 × P(Immediate)

이 기준에서는 점수 범위가 02다. 공식 예시의 0.8308은 그 범위 안의 가중 평균이며, 특정 수준으로 반올림된 값도 01로 정규화된 값도 아니다.

다음 비교는 자동 처리 정책에서 혼동하기 쉬운 응답값을 각각의 해석 축에 놓는다.

⤢✕ System One 응답값의 해석 경계 비교 축: 값의 대상 · 범위 · 해석 항목 값의 대상 범위 해석 choice의 probabilities noul confidence 긴급도 score 제공한 후보별 확률 false와 true 후보 사이에서 true의 확률 전체 후보 분포의 집중도 Routine·Soon·Immediate의 인덱스 0·1·2를 확률로 가중 평균한 값 각 값 0~1 0~1 0~1 0~2 합은 부동소수점 정밀도 범위에서 1 Boolean이 아닌 숫자 0은 균등 분포 1에 가까우면 한 후보가 지배 정답 보장이 아님 예시 값 0.8308 수준으로 반올림하거나 0~1로 정규화한 값이 아님 Ollama System One으로 확률 기반 티켓 분류와 모델 라우팅 만들기

운영 임계값은 실제 티켓과 기대 라벨을 대조하면서 정하는 편이 좋다. choice의 후보별 확률, noul의 조건 확률, score의 수준 범위를 구분해 평가하고, confidence가 높은 오분류도 확인해야 한다. 자료에는 공통으로 적용할 임계값이나 정답률이 제시되어 있지 않으므로 특정 수치를 기본값으로 삼을 근거는 없다.

질문을 묶어도 답이 다음 질문으로 넘어가지는 않는다

하나의 티켓에서 환불 요청 여부와 긴급도를 함께 확인할 수 있다. 공식 문서는 문자열뿐 아니라 객체 형태의 state도 보여 준다.

curl http://localhost:11434/v1/systemone \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "nimble",
    "state": {"ticket": "I was charged twice. Please refund the extra payment."},
    "questions": {
      "refund": {
        "type": "noul",
        "instructions": "Is the customer requesting a refund?",
        "criteria": {
          "false": "No refund is requested",
          "true": "The customer requests a refund"
        }
      },
      "urgency": {
        "type": "score",
        "instructions": "How urgently does this ticket need a response?",
        "criteria": [
          "Routine: no time pressure",
          "Soon: a customer is inconvenienced",
          "Immediate: a critical service is unavailable"
        ]
      }
    }
  }'

공식 예시는 환불 확률 0.9989와 0~2 범위의 긴급도 점수 0.8308을 반환한다고 설명한다. 환불 요청이라는 조건과 응답 긴급도는 서로 다른 판단이므로 각 결과에 별도의 정책을 적용해야 한다.

여기서 질문을 묶는 방식은 연쇄 판단이 아니다. 스키마에 따르면 각 질문은 전체 state와 해당 질문 스키마를 대상으로 따로 평가되며, 답은 뒤의 질문으로 전달되지 않는다. urgency가 앞선 refund의 결과를 입력으로 받는다고 가정해서는 안 된다.

다음 그림은 공유 입력에서 각 질문으로 갈라지는 평가 관계를 보여 준다. 화살표는 질문의 실행 순서를 뜻하지 않는다.

⤢✕공유 staterefund 질문urgency 질문환불 확률긴급도 점수애플리케이션 정책

환불 결과에 따라 다음 질문의 내용을 바꾸는 절차가 필요하다면, 그 연결은 애플리케이션에서 구성해야 한다. 반대로 같은 입력에 대해 독립적인 판단이 필요하다면 질문을 한 요청에 묶을 수 있다. 질문은 최대 64개까지 허용된다.

state와 instructions에 객체나 배열을 넣으면 JSON 텍스트로 직렬화된다. 이를 채팅 메시지나 멀티모달 입력으로 해석하는 기능은 아니다.

요청 크기와 컨텍스트를 별도로 점검한다

운영 전에는 본문 크기 제한과 모델 컨텍스트 제한을 모두 확인해야 한다. 요청은 64 KiB 안에 들어가야 하고, 각 질문에 대해 렌더링된 프롬프트는 로드된 컨텍스트 창 안에 들어가면서 점수 계산용 토큰 위치를 2개 남겨야 한다. 입력은 자동으로 절단되지 않는다.

따라서 요청 본문이 64 KiB보다 작다는 사실만으로 컨텍스트 조건까지 충족했다고 판단할 수 없다. 긴 티켓에 질문 설명과 기준을 붙였을 때 렌더링된 입력이 한도를 넘으면 오류가 된다. 질문 수 역시 최대 64개라는 허용 범위와 별개로 입력 및 기준의 길이를 고려해야 한다.

HTTP 상태 스키마에 명시된 원인 확인할 대상
400 잘못된 요청, 지원하지 않는 모델·러너, 렌더링된 프롬프트의 컨텍스트 초과 필드와 기준 구조, 모델 호환성, 질문별 입력 길이
404 로컬 모델을 찾을 수 없음 요청한 모델의 다운로드 여부
413 요청 본문이 64 KiB 초과 직렬화된 요청 본문 크기
500 모델 로딩·렌더링·점수 계산 실패 실패가 발생한 처리 구간

모델 호환성도 요청 전에 확인할 조건이다. System One은 호환되는 GGUF 가중치와 점수 계산이 가능한 러너를 필요로 하며, 클라우드 모델과 MLX/Safetensors 모델은 지원하지 않는다. 이들은 400 오류 대상이다.

응답은 JSON 하나이며 스트리밍, 이미지, 툴, 생성 제어 옵션은 지원하지 않는다. 기존 생성 요청을 옮길 때는 분류에 필요한 model, state, questions를 중심으로 요청을 구성해야 한다.

모델을 요청 뒤에도 로드된 상태로 유지할 시간은 keep_alive로 지정할 수 있다. 기간 문자열이나 초 단위 숫자를 받으며, 0이면 요청 뒤에 내리고 음수면 계속 로드해 둔다. 기본값은 서버 설정을 따르며 별도 설정이 없으면 5m이다.

사용량과 운영 판단을 함께 기록한다

usage.input_tokens는 각 질문의 전체 렌더링 프롬프트 길이를 합산한다. 공유 컨텍스트가 캐시되더라도 반복된 부분이 집계에 포함된다. 질문을 한 요청에 묶었다는 이유만으로 토큰 집계에서 공유 입력이 한 번만 계산된다고 가정하면 안 된다.

usage.output_tokens는 응답 JSON의 길이가 아니다. 점수 계산을 위해 내부적으로 생성한 토큰이며 접두부 준비와 재시도도 포함한다. 질문 수보다 클 수 있다. 앞선 라벨 분류 예시의 174 입력 토큰과 1 출력 토큰도 해당 예시의 값으로 읽어야 한다.

티켓 분류를 적용할 때는 선택된 라벨, 후보별 확률, confidence, 적용한 임계값과 최종 경로를 함께 기록하는 방식을 권한다. 긴급도 점수를 쓴다면 기준 배열도 결과 해석에 필요하다. 기준을 바꾸면 같은 숫자가 가리키는 수준도 달라질 수 있기 때문이다.

모델 라우팅 역시 선택된 키를 실제 경로에 연결한 뒤, 자동 라우팅 조건을 충족하지 못한 결과를 어떻게 처리할지 정해야 한다. System One이 제공하는 것은 후보 사이의 판단값이다. 그 값을 업무의 처리 결정으로 바꾸는 정책까지 명시해야 확률 기반 분류가 운영 흐름으로 이어진다.

OllamaSystem One로컬 AI티켓 분류모델 라우팅