LLM에게 "JSON으로 답해줘"라고 부탁하는 대신, 처음부터 소프트웨어가 쓸 수 있는 판단을 받아오는 방법이다.
생성형 AI 중심의 시스템에서 벗어나 AI는 판단하고 코드는 통제하는 자동화 아키텍처이다.
department billing confidence 1.00
{billing: 1.00, technical: 0.00, sales: 0.00, other: 0.00}
refund 0.98
urgency 2.81 confidence 0.81
기존 LLM은 질문을 던지면 사람의 언어(줄글)로 대답하기 때문에, 프로그램에서 그 결괏값을 활용하려면 필요한 정보만 따로 떼어내는 '파싱(Parsing, 구문 분석)' 작업이 필요하다(예: 텍스트에서 JSON 형태만 추출). 반면, Jev는 문장을 생성하는 대신 처음부터 결괏값을 구조화된 데이터 형태로 반환하므로 번거로운 추출 과정이 필요 없다는 의미이다.
LLM은 보통 자신이 내놓은 답변에 대해 얼마나 확신하는지 수치로 알려주지 않는다. 하지만 Jev는 결괏값과 함께 그 판단이 맞을 확률과 확신도(Confidence)를 구체적인 숫자(예: 0.98, 1.00)로 함께 제공한다. 즉, AI가 낸 답을 '얼마나 믿고 써도 될지'를 개발자가 수치로 파악하고 제어할 수 있다는 뜻이다.

(출처: wikidocs)
기존 LLM은
오해 : LLM을 대체하는 것이 아닌 LLM 앞에 판단 계층을 하나 두는 것이다.

(출처: wikidocs)
💡 중요하게 판단해야 하는 것은 : 이 방식이 이 task에 적합한가이다.
다음에 올 토큰을 하나씩 예측해서 이어 붙이는 방식이다.
ex) 배고프다 라는 입력을 받으면, 다음에 올 법한 조각을 고르고, 그 결과를 다시 입력에 붙여 그 다음 조각을 고른다 → 문장이 끝날 때까지 이 과정을 반복한다.
그로 인한 영향
1. 출력이 길어질수록 시간이 오래 걸린다.
2. 출력은 언제나 문자열이다 → 출력할 때 JSON 형태로 라는 걸 강제해야 한다.
3. 자유로움이 높아서, 실행할 때마다 답이 다르게 나온다.
특히, 속도와 비용 + 확신도가 가장 큰 문제다.
| 생성(Generation) | 결정(Decision) | |
|---|---|---|
| 답의 범위 | 정해져 있지 않음 | 미리 정해져 있음 |
| 좋은 답 | 여러 개일 수 있음 | 보통 하나 |
| 출력 형태 | 문자열 | 선택지, 숫자, 참/거짓 |
| 길이 | 길수록 좋을 때도 있음 | 길이가 의미 없음 |
| 사람이 하는 일 | 읽는다 | 그대로 실행한다 |
"이 문서를 요약해줘"는 생성이다. 정답이 하나가 아니고, 여러 요약문이 모두 괜찮을 수 있다. 반면 "이 문의를 billing/technical/sales/other 중 어디로 보낼까"는 결정이다. 답은 넷 중 하나이고, 코드는 그 답을 받아 바로 분기한다.
"이건 할랄/하람/마슈부 중에 뭐야?"라고 질문한다.
LLM에게 질문했을 때
halal
판단 전용 모델 (Jev)
halal 0.90
haram 0.00
mashbooh 0.10
| System One Model (Jev) | 일반 LLM | Reasoning LLM | |
|---|---|---|---|
| 하는 일 | 판단 | 생성 | 추론 |
| 출력 | 구조화된 값 + 확률 | 문자열 | 문자열 + 생각 과정 |
| 속도 | 빠름 | 보통 | 느림 |
| 어울리는 문제 | 후보가 정해진 반복 판단 | 글쓰기, 요약, 대화 | 수학, 코드, 다단계 문제 |
함께 쓴다면 다음과 같다.

(출처: wikidocs)
⇒ "작업의 성격과 도구의 성격을 맞추자"
result = client.system_one(
state="결제가 두 번 됐습니다. 빨리 환불해주세요.",
questions={
"department": Choice(criteria={"billing": None, "technical": None,
"sales": None, "other": None}),
"urgency": Score(criteria=["급하지 않음", "보통", "급함", "매우 급함"]),
"refund": Noul(instructions="고객이 환불을 요구하고 있는가?"),
},
)
→ 이 구조를 기반으로 Jev는 여러 질문을 서로 간섭하지 않게 평가하도록 설계되어 있다.
| 질문 | 묻는 것 | 예 |
|---|---|---|
| Choice | 여러 후보 중 어느 것인가 | 어느 부서로 보낼까 |
| Noul | 그런가, 아닌가 | 환불을 요구하는가 |
| Score | 어느 정도인가 | 얼마나 급한가 |
from typesafe_sdk import TypeSafeClient, Choice
with TypeSafeClient() as client:
result = client.system_one(
state="앱이 로그인 화면에서 계속 멈춰요.",
questions={
"department": Choice(criteria={
"billing": None, "technical": None,
"sales": None, "other": None,
})
},
)
answer = result.choices["department"]
print(answer.choice) # technical
print(answer.confidence) # 1.0
print(answer.probabilities) # {'technical': 1.0, 'billing': 0.0, ...}
| 필드 | 내용 |
|---|---|
| choice | 확률이 가장 높은 후보의 이름 |
| confidence | 그 선택에 대한 확신도, 0에서 1 |
| probabilities | 후보 전체의 확률, 합은 1 |
후보를 잘 나누는 법
1. 후보는 서로 겹치지 않게 만든다. → 반반 상황이 생길 수도 있다.
2. 빠진 후보가 있는지 confidence로 확인한다.
3. Other은 넣되 기대하지 않는다.
4. 후보 개수는 늘려도 느려지지 않는다.
from typesafe_sdk import TypeSafeClient, Noul
with TypeSafeClient() as client:
result = client.system_one(
state="전액 환불해주세요",
questions={"refund": Noul(instructions="고객이 환불이나 결제 취소를 요구하고 있는가?")},
)
print(result.nouls["refund"].noul) # 0.98
후보 대신 순서가 있는 단계 목록을 받는다.
from typesafe_sdk import TypeSafeClient, Score
with TypeSafeClient() as client:
result = client.system_one(
state="며칠째 답이 없네요. 확인 부탁드립니다",
questions={"sentiment": Score(criteria=["차분함", "약간 불만", "화남", "매우 화남"])},
)
answer = result.scores["sentiment"]
print(answer.score) # 0.91
print(answer.confidence) # 0.91
print(answer.legend) # {0: '차분함', 1: '약간 불만', 2: '화남', 3: '매우 화남'}
print(answer.probabilities) # {0: 0.09, 1: 0.91, 2: 0.0, 3: 0.0}
실습 환경
conda create -n jev python=3.12 -y
conda activate jev
pip install typesafe-sdk python-dotenv openai
예제 코드