채팅 모델로 텍스트를 분류하려고 하면 보통 다음과 같은 흐름이 됩니다. 프롬프트 마지막에 "JSON 형식으로만 답변해 주세요"라고 작성하고, 반환된 문자열을 파싱하며, 만약 "네! 카테고리는 다음과 같습니다:"부터 시작되었다면 재시도(retry) 처리를 넣습니다. 이렇게 해도 작동하지만, 5개의 레이블 중 하나를 선택하는 작업치고는 주변 코드가 너무 많습니다.
결정 모델(decision model)은 다른 접근 방식을 취합니다. 입력과 수용 가능한 답변 후보들을 전달하면, 모델은 각 후보에 점수를 매기고 모든 후보에 대한 확률을 반환합니다. 텍스트를 전혀 생성하지 않기 때문에, 후보에 없는 답변이 돌아올 염려가 없습니다.
본 기사에서는 로컬에서 추론(inference)을 수행하는 Rust 기반 라이브러리 NobodyWho의 Python 바인딩을 사용하여 문의 티켓 분류를 구현합니다. 결정 모델은 10월 29일 릴리스(Python 버전 v5.0.0)로 추가되었습니다.
본인은 NobodyWho의 기여자입니다 (핵심 팀 멤버는 아닙니다).
pip install -U "nobodywho>=5.0.0"
from nobodywho import Decision, Model
model = Model(model_path="hf://ggml-org/Laya-GGUF/Laya-Q8_0.gguf")
decisions = Decision(model=model, n_ctx=1024)
사용하는 모델은 Laya입니다. 0.4B 규모의 ModernBERT 기반 모델이며, Q8_0 GGUF 파일 크기는 449MB이고 라이선스는 Apache-2.0입니다. 추론은 같은 프로세스 내에서 작동하므로 서버 구동이나 API 키가 필요 없습니다. 다운로드한 파일을 사용할 경우에는 model_path에 로컬 경로를 전달합니다.
message = "I was charged twice. Please refund the duplicate payment."
questions = {
"department": {
...
질문 유형마다 반환되는 값의 형태가 다릅니다.
- 선택형 (Choice): 후보 중 하나를 선택합니다.
choice
result["answers"]["department"]["choice"]
이 선택된 후보이며, ["probabilities"]에는 모든 후보의 확률이 들어 있습니다. -
- 예/아니오 질문 (Yes/No):
noul
result["answers"]["refund"]["noul"]는 "예"일 확률(0~1)입니다. -
- 순서형 척도 (Ordered Scale): 입력을 2~10단계의 순서가 있는 척도 위에 위치시킵니다. 단계는 0부터 번호가 매겨지므로, 이 예시에서
score
1.6
이라면 "soon"과 "urgent" 사이입니다. 값은 확률의 가중 평균이므로, "urgent" 자체의 확률을 알고 싶다면 probabilities["2"]를 읽습니다.
또한, result["usage"]["output_tokens"]는 항상 0입니다.
실제 파이프라인에서 결정 모델이 유용한 곳은 바로 여기입니다. 모든 후보의 확률을 얻을 수 있기 때문에, 판단에 확신이 없는 경우만 사람에게 전달할 수 있습니다.
dept = result["answers"]["department"]
if dept["confidence"] < 0.6:
send_to_human(message)
...
send_to_human과 route는 자신의 코드로 대체해 주세요. 0.6은 임의의 예시이므로, 자신의 데이터로 조정해야 합니다.
문서에도 나와 있듯이, confidence는 확률이 하나의 후보에 얼마나 집중되어 있는지를 나타내는 값이며, 높다고 해서 답변이 반드시 정확한 것은 아닙니다. 이 임계값을 신뢰하기 전에 실제 티켓 수백 건으로 검증해 보는 것을 추천합니다.
import asyncio
from nobodywho import DecisionAsync
async_decisions = DecisionAsync(model=model, n_ctx=1024)
...
DecisionAsync의 생성자 자체는 동기적으로 호출됩니다.
현재 지원되는 결정 모델은 Laya뿐입니다. Laya 자체의 README에 따르면, 타입 지정 판별(typed classification) 벤치마크에서의 정확도는 제로샷(zero-shot) 기준으로 0.362이며, 도메인에 맞춰 파인튜닝(fine-tuning)할 경우 0.766입니다. 중요한 분류에 사용하기 전에 반드시 자신의 데이터로 측정해 보십시오.
일본어 입력에 대한 정확도는 미검증입니다. 이 문서의 예시는 모두 영어입니다. 만약 일본어 티켓으로 테스트한 분이 있다면, 결과를 댓글로 알려주시면 감사하겠습니다. - 선택만 할 수 있습니다. 이유 설명이나 텍스트 추출이 필요하다면, 생성 모델(generative model)은 여전히 필요합니다.
- 결정 모델은 Python 외에도 Swift, Kotlin, Flutter, React Native, Godot의 바인딩으로 같은 버전에서 출시되었습니다. 모바일 앱 내에서도 동일한 판별을 실행할 수 있습니다.
문서: https://docs.nobodywho.ooo/python/decision-models
리포지토리: https://github.com/nobodywho-ooo/nobodywho