Noul
Noul 질문은 TypeSafe 모델에게 yes/no 질문을 평가하도록 요청하고 답이 yes일 확률을 반환받습니다.
답변이 yes 또는 no일 때 Noul을 사용하십시오. 예를 들어 이 메시지가 환불을 요청하는지, 이 이력서가 분산 시스템을 언급하는지, 이 댓글이 개인 정보를 포함하는지 등입니다. 답변이 여러 선택지 중 하나라면 Choice를 사용하십시오. 스펙트럼 위의 위치라면 Score를 사용하십시오. 질문 유형 선택하기에서 세 가지를 모두 비교합니다.
Noul 답변은 답이 yes일 확률을 나타내는 단일 숫자이며, 0은 no, 1은 yes를 뜻합니다.
요청 구조
TypeSafe API로 보내는 POST 요청 본문은 다른 질문 유형과 마찬가지로 세 개의 최상위 필드를 가집니다. 평가할 콘텐츠인 state, model, 그리고 questions입니다. 각 Noul 질문에는 다음 필드가 있습니다.
type: 항상"noul"입니다.instructions: 모델이 답하는 yes/no 질문, 또는 모델이 판단할 진술문입니다.criteria: 선택 사항입니다. yes와 no가 무엇을 의미하는지에 대한true와false설명을 담은 객체입니다.
아래는 상태가 지원 메시지이고 두 질문이 각각 고객이 사람을 원하는지, 이전에 지원팀에 연락한 적이 있는지인 요청입니다.
{
"state": "I have asked three times now. Can I please just talk to a real person?",
"questions": {
"is_human_escalation": {
"type": "noul",
"instructions": "Is the customer asking for a human agent?"
},
"is_repeat_contact": {
"type": "noul",
"instructions": "Has the customer contacted support about this before?",
"criteria": {
"true": "Mentions a prior attempt, ticket, or that they have asked before",
"false": "No sign of any previous contact"
}
}
}
}질문 id는 여러분이 고르며, 여기서는 is_human_escalation과 is_repeat_contact입니다. 이 id는 모델에 전송되지 않습니다. 각 답변은 같은 id 아래에 반환됩니다. 첫 번째 질문은 instructions에만 의존합니다. 두 번째는 무엇이 yes이고 무엇이 no인지 말하기 위해 criteria를 추가합니다.
Python SDK에서는 같은 질문이 Noul 객체입니다.
from typesafe_sdk import Noul, NoulCriteria, TypeSafeClient
with TypeSafeClient() as client:
response = client.system_one(
model="jev-latest",
state="I have asked three times now. Can I please just talk to a real person?",
questions={
"is_human_escalation": Noul(
instructions="Is the customer asking for a human agent?",
),
"is_repeat_contact": Noul(
instructions="Has the customer contacted support about this before?",
criteria=NoulCriteria(
true="Mentions a prior attempt, ticket, or that they have asked before",
false="No sign of any previous contact",
),
),
},
)
print(response.answers["is_human_escalation"].noul)
print(response.answers["is_repeat_contact"].noul)
system_one 메서드와 https://api.typesafe.ai/v1/systemone 엔드포인트는 모두 TypeSafe의 AI 모델인 System One의 이름을 따서 명명되었습니다. 코드의 어디에서 사용할지는 TypeSafe로 구축하는 방법에서 다룹니다.
코딩 에이전트를 사용한다면, 요청과 응답 형태를 알 수 있도록 먼저 TypeSafe 에이전트 스킬을 설치하십시오.
응답 구조
응답에는 요청의 id 아래에 질문당 answers 항목이 하나씩 있습니다.
{
"model": "jev-1.13.0",
"answers": {
"is_human_escalation": {
"type": "noul",
"noul": 0.99
},
"is_repeat_contact": {
"type": "noul",
"noul": 0.93
}
},
"usage": {
"input_tokens": 360,
"output_tokens": 39
}
}
여기서 두 답변 모두 1에 가깝습니다. 고객이 “talk to a real person”이라고 말하므로 is_human_escalation은 0.99입니다. “I have asked three times now”는 is_repeat_contact의 true 설명과 일치하므로 0.93입니다.
Noul 읽기
그 숫자는 답변과 확실성을 하나로 담고 있습니다. 1에 가까운 값은 강한 yes입니다. 0에 가까운 값은 강한 no입니다. 0.5에 가까운 값은 모델이 yes와 no에 비슷한 확률을 부여한다는 뜻입니다.
아래 표는 서로 다른 고객 메시지에 대한 is_human_escalation 질문의 기록된 jev-1.13.0 답변을 보여줍니다.
| 상태 | noul |
|---|---|
| Thanks, that fixed it! | 0.02 |
| How do I reset my password? | 0.07 |
| I need this sorted today, whatever it takes. | 0.26 |
| Are you a bot? | 0.40 |
| Is there any way to speak to someone about my invoice? | 0.84 |
| I have asked three times now. Can I please just talk to a real person? | 0.99 |
처음 두 개와 마지막 두 개는 명확합니다. “I need this sorted today”는 긴급하지만 사람을 요청하지 않으며 0.26을 받습니다. “Are you a bot?“은 사람을 원한다는 암시는 있지만 요청하지는 않으며, 모델이 0.40으로 거의 반반으로 나눕니다. 둘 다 코드의 임계값을 기준으로 결정을 내려야 하는 종류의 메시지입니다.
Choice나 Score와 달리 Noul에는 별도의 confidence 값이 없습니다. Noul의 확률 분포에는 yes와 no라는 두 결과만 있으므로 단일 noul 값이 그것을 완전히 설명합니다. Choice나 Score는 확률을 여러 선택지나 레벨에 퍼뜨리고, confidence가 그 퍼짐을 요약합니다.
가장 흔하게 코드는 noul에 임계값을 적용해 불리언으로 만듭니다.
wants_human = response.answers["is_human_escalation"].noul > 0.9
if wants_human:
route_to_agent(ticket)
else:
route_to_bot(ticket)
임계값을 어디에 둘지는 틀렸을 때의 비용에 달려 있습니다. yes와 no가 행동하기에 똑같이 쉬우면 0.5를 사용하십시오. 거짓 yes에 행동하는 것이 비쌀 때, 예컨대 누군가를 호출하거나 환불을 지급할 때는 올리십시오. 진짜 yes를 놓치는 것이 비쌀 때, 예컨대 안전 문제를 표시하지 못할 때는 내리십시오. 중간 값은 어느 코드 경로도 아니라 사람에게 보낼 수 있습니다. 이는 신뢰도 페이지가 Choice 및 Score 답변에 대해 설명하는 것과 같은 세 갈래 분할입니다.
Noul 값은 0에서 1까지이지만, 여러분이 물은 대상의 척도가 아닙니다. 답이 yes일 확률입니다. 질문이 실제로 정도에 관한 것이라면 그 값은 정도를 측정하지 않습니다. 아래에서는 “Is the candidate strong in Python?“을 네 명의 지원자에 대해 질문하며, 경험 없음, 어느 정도 익숙함, 직장에서의 정기적 사용, 깊은 전문성이라는 네 레벨의 Score와 나란히 둡니다.
| 지원자 | Noul: “Is the candidate strong in Python?” | Score: “How much Python experience does the candidate have?” |
|---|---|---|
| My experience is in Java and Go. I have not used Python. | 0.03 | 0.0 (No experience) |
| I have used Python occasionally for small scripts alongside my main Java work. | 0.14 | 1.0 (Some familiarity) |
| I used Python every day for two years in my last job, mostly data pipelines. | 0.81 | 2.05 (Regular use in a job) |
| I have written Python daily for eight years, including maintaining a large Django codebase. | 0.92 | 2.89 (Deep expertise) |
Noul은 “강하다”라는 하나의 명제를 판단하며, 값은 그것이 얼마나 그럴듯한지를 나타냅니다. 코드에서 0에서 1 범위에 레벨을 만들 수도 있습니다. 예컨대 “어느 정도 경험”에 0.3에서 0.7을 부여할 수 있습니다. 하지만 모델은 그것들을 보지 못하므로, 답변에서 그 레벨을 기준으로 판단된 것은 아무것도 없습니다. 중간 값은 중간 정도의 경험이나 불명확한 사례를 뜻할 수 있고, 지원자 사이의 간격은 여러분이 고른 것이 아닙니다. Score는 각 레벨 설명을 개별적으로 판단하므로 모든 지원자가 여러분이 쓴 레벨에 또는 그 근처에 자리 잡았고, 반환된 확률은 모델이 레벨 사이에서 판단을 어떻게 나눴는지 보여줍니다. 동의하지 않으면 레벨의 표현을 바꾸고 다시 실행하십시오. 이 구분은 질문 유형 선택하기에서 설명합니다.
Noul 질문 작성하기
Noul당 하나의 yes/no 질문을 하십시오. 질문에 두 조건이 있으면, 예컨대 “고객이 화났으면서 환불을 요청합니까?“라면 모델이 둘을 한꺼번에 판단해야 하고 값의 의미가 줄어듭니다. Noul 두 개를 질문하고 코드에서 결합하십시오.
높은 값이 yes를 뜻하도록 질문을 표현하십시오. “이 메시지에 개인 정보가 포함되어 있습니까?“는 명확합니다. “이 메시지에 개인 정보가 없습니까?“는 의미를 뒤집으며, 나중에 이를 읽는 코드는 거꾸로 받아들이게 됩니다.
진술문도 질문만큼 잘 작동합니다. “The customer is requesting a refund”의 경우 1에 가까운 값은 그 진술이 참이라는 뜻입니다. 자체 데이터로 두 표현을 모두 시도해 어느 쪽이 더 나은지 보십시오.
yes와 no 사이의 경계를 모호하지 않게 만드십시오. “Does this candidate have any Python experience?“는 “any”가 중간 지대를 남기지 않으므로 잘 작동합니다. 경계가 미묘하면 위의 is_repeat_contact 질문처럼 true와 false 설명이 담긴 criteria를 추가하십시오. 대부분의 Noul에는 instruction으로 충분하므로, criteria가 있는 경우와 없는 경우로 질문을 시도해 보고 여러분의 문서에서 더 나은 답을 주는 쪽을 유지하십시오.
모범 사례: 호출당 둘 이상의 질문을 하십시오
조건 체크리스트의 경우, 하나의 요청에 많은 Noul 질문을 하십시오. 조건당 질문 하나이고, 그 조합이 무엇을 의미하는지는 코드가 결정합니다. 질문은 병렬로 평가되므로 Noul을 추가해도 응답 시간은 거의 변하지 않습니다. 여러 질문을 함께 하기에서 더 자세히 설명합니다.
코드에서 여러 Noul 답변 처리하기
위의 두 질문 요청은 메시지를 라우팅하기에 충분한 정보를 코드에 줍니다. 아래 예시는 고객이 사람을 요청하면 사람에게 에스컬레이션하고, 이전에 연락한 적이 있으면 우선순위를 올립니다. 두 질문 중 하나라도 중간 값이면 코드 경로 대신 검토자에게 보냅니다.
from typesafe_sdk import Noul, NoulCriteria, TypeSafeClient
SUPPORT_QUESTIONS = {
"is_human_escalation": Noul(
instructions="Is the customer asking for a human agent?",
),
"is_repeat_contact": Noul(
instructions="Has the customer contacted support about this before?",
criteria=NoulCriteria(
true="Mentions a prior attempt, ticket, or that they have asked before",
false="No sign of any previous contact",
),
),
}
YES = 0.8
NO = 0.2
def route(message: str) -> None:
with TypeSafeClient() as client:
response = client.system_one(
model="jev-latest",
state=message,
questions=SUPPORT_QUESTIONS,
)
answers = response.answers
wants_human = answers["is_human_escalation"].noul
repeat = answers["is_repeat_contact"].noul
if NO < wants_human < YES or NO < repeat < YES:
# The model isn't sure either way. Let a person decide.
send_to_review(message)
return
priority = "high" if repeat > YES else "normal"
if wants_human > YES:
route_to_agent(message, priority=priority)
else:
route_to_bot(message, priority=priority)
위 메시지의 경우 is_human_escalation의 noul 답변 값은 0.99이고 is_repeat_contact는 0.93이므로, 코드는 높은 우선순위로 상담원에게 라우팅합니다. “How do I reset my password?” 메시지는 두 질문 모두 0.07이고 봇으로 라우팅됩니다.
임계값은 여러분의 코드에 있습니다. 검토자가 너무 많은 메시지를 본다면 NO와 YES 사이의 간격을 좁히십시오. 잘못된 라우팅이 너무 많이 통과한다면 넓히십시오. 나중에 메시지가 결제를 언급하는지, 개인 정보를 포함하는지 알아야 하면 SUPPORT_QUESTIONS에 Noul을 하나 더 추가하십시오. 요청 횟수는 하나로 유지됩니다.
구조화된 instructions
Instructions는 문자열 대신 객체가 될 수 있으며, 질문을 한 필드에, 보조 데이터를 다른 필드에 둡니다. 언제 그것이 도움이 되는지는 질문에 구조 사용하기에서 다룹니다. 여기서는 코드로 만든 질문에 사용됩니다. 방금 도착한 이력서를 같은 사람일 수 있는 후보 데이터베이스의 레코드와 비교합니다. 각 레코드는 그대로 potential_duplicate 필드에 들어가고, question은 모든 레코드에 대해 같으며, 모든 레코드가 하나의 요청에서 확인됩니다. 코드가 생성한 질문 키에는 각 레코드의 데이터베이스 ID가 들어 있습니다.
{
"state": {
"resume": {
"name": "John Smith",
"location": "Oakland, CA",
"summary": "Backend engineer with eight years of Python and Go experience.",
"experience": [
{
"employer": "Google",
"title": "Senior Backend Engineer",
"years": "2021-2025"
},
{
"employer": "Microsoft",
"title": "Software Engineer",
"years": "2017-2021"
}
]
}
},
"questions": {
"same_as_record_18": {
"type": "noul",
"instructions": {
"potential_duplicate": {
"name": "Jon Smith",
"location": "Oakland, CA",
"last_employer": "Google"
},
"question": "Is the resume for the same person as `potential_duplicate`?"
}
},
"same_as_record_42": {
"type": "noul",
"instructions": {
"potential_duplicate": {
"name": "John Smith",
"location": "Austin, TX",
"last_employer": "Lone Star Freight"
},
"question": "Is the resume for the same person as `potential_duplicate`?"
}
},
"same_as_record_77": {
"type": "noul",
"instructions": {
"potential_duplicate": {
"name": "John Smithers",
"location": "Oakland, CA",
"last_employer": "Bay Health Clinic"
},
"question": "Is the resume for the same person as `potential_duplicate`?"
}
}
}
}응답:
{
"model": "jev-1.13.0",
"answers": {
"same_as_record_18": {
"type": "noul",
"noul": 0.74
},
"same_as_record_42": {
"type": "noul",
"noul": 0.09
},
"same_as_record_77": {
"type": "noul",
"noul": 0.08
}
},
"usage": {
"input_tokens": 535,
"output_tokens": 58
}
}
각 답변은 이력서가 그 레코드의 사람에 대한 것일 확률입니다. 레코드 18은 이름 철자가 다르지만 위치와 고용주가 일치하여 0.74를 받습니다. 레코드 42는 다른 도시에 같은 이름이 있고 고용주가 달라 0.09를 받습니다. 레코드 77은 같은 위치에 비슷한 이름이 있고 고용주가 달라 0.08을 받습니다. 코드에서 여러 Noul 답변 처리하기에서처럼 코드에서 각 값에 임계값을 적용하고, 중간 값은 사람에게 보내십시오.
Python SDK에서는 질문이 후보 레코드로부터 만들어집니다. 질문 텍스트는 고정되고 레코드가 바뀝니다.
from typesafe_sdk import Noul, TypeSafeClient
SAME_PERSON = "Is the resume for the same person as `potential_duplicate`?"
def duplicate_questions(candidates: list[dict]) -> dict[str, Noul]:
"""One Noul per candidate record, all asking the same question."""
return {
f"same_as_record_{candidate['id']}": Noul(
instructions={
"potential_duplicate": {
"name": candidate["name"],
"location": candidate["location"],
"last_employer": candidate["last_employer"],
},
"question": SAME_PERSON,
},
)
for candidate in candidates
}
def find_duplicates(resume: dict, candidates: list[dict]) -> list[str]:
with TypeSafeClient() as client:
response = client.system_one(
model="jev-latest",
state={"resume": resume},
questions=duplicate_questions(candidates),
)
return [
question_id
for question_id, answer in response.answers.items()
if answer.noul > 0.7
]
structured-data-extraction 캐스케이드 쿡북은 구조화된 instructions를 사용해 추출된 레코드를 검증합니다. 모든 필드에 같은 질문 집합이 적용됩니다. 각 질문의 instructions 객체에는 질문 텍스트가 main_question 속성에 들어 있습니다. 또한 필드마다 바뀌는 field_spec과 extracted_field 속성도 있습니다.
쿡북 속의 Noul
Noul 질문을 사용하는 앱을 보려면 저희 쿡북을 살펴보십시오.
- 병렬 질문은 하나의 기사에 대해 13개 질문으로 된 규제 체크리스트를 하나의 요청으로 실행합니다.
- 자기 일관성: nouls는 보험 청구를 15개 질문 루브릭에 대해 점수화하고 실행 간 값이 얼마나 안정적인지 측정합니다.
- 재순위화는 임계값이 아니라 확률 자체를 사용합니다. 질의-후보 쌍당 Noul 하나를 두고, 값으로 후보를 정렬합니다.
- 줄 단위 검색은 일치하는 줄을 찾는 Choice와 문서에 답이 있는지 확인하는 Noul을 짝지어 사용합니다.
- 구조 복구는 줄 쌍마다 줄 바꿈이 문장을 나눴는지 Noul 하나를 질문하여, 일반 텍스트에서 문단을 재구성합니다.