가이드
튜토리얼: 분류기 또는 구조화된 출력
지원 문의에 레이블을 붙이는 Python 함수를 평가하고, 릴리스가 차단된 이유를 읽고, 놓친 케이스를 고치고, 그 수정을 원래 버전과 비교합니다. 모두 계정, 네트워크, 모델 없이 여러분의 머신에서 이루어집니다.
만들 것
answer와 label(refund, account 또는 other)을 담은 JSON 객체를 반환하는 지원 봇입니다. 이 봇에 세 가지 요구 사항을 적용합니다. 레이블이 충분히 자주 맞을 것, 출력이 항상 올바른 형태일 것, 그리고 어떤 답변도 미국 사회보장번호처럼 보이는 것을 노출하지 않을 것. 그중 두 가지는 참조 답이 필요 없는 형식 검사이고, 하나는 참조 레이블에 대한 작업 성공을 측정합니다. 이 차이는 중요하며, 이 페이지는 둘을 구분해 다룹니다.
아래에서 쓰는 용어(케이스, 실행, 지표, 구간, 규칙, 게이트)는 핵심 개념에 정의되어 있습니다.
사전 준비
- Python 3.11 이상.
- 가상 환경에 설치된 Oloproof:
python3 -m venv .venv
. .venv/bin/activate
pip install oloproof- 패키지와 함께 제공되는 예제 프로젝트와 후보 변경. 둘 다 새 디렉터리로 복사하고 첫 번째 디렉터리에서 작업합니다. 모든 파일이 아래에도 나와 있으므로 직접 입력해도 됩니다:
oloproof init --example support_bot support-classifier
oloproof init --example classification support-change
cd support-classifier이 페이지 어디에서도 API 키, 제공자 계정, 네트워크 접근을 사용하지 않습니다.
파일
support-classifier/
app.py the application under test (a Python callable)
oloproof.yaml the suite: dataset, system, evaluators
release.yaml the release policy: rules the run is decided against
data/support.jsonl 18 cases, one JSON object per line
rubrics/helpful.md a judge rubric, unused here모든 명령은 support-classifier/ 디렉터리에서 실행하십시오. Oloproof는 그곳의 .oloproof/에 저장소를 둡니다. 처음부터 다시 시작하려면 그 디렉터리를 삭제하십시오.
애플리케이션과 어댑터
애플리케이션은 어댑터를 통해 호출됩니다. Python 애플리케이션의 어댑터는 함수 그 자체입니다. Oloproof는 그 함수를 import하고, 케이스마다 케이스의 input으로 한 번씩 호출하며, 반환된 딕셔너리를 그 케이스의 출력으로 기록합니다.
# app.py
from typing import Any
from oloproof import system
@system(name="support-bot", version="slice-a-example")
def answer(case: dict[str, Any]) -> dict[str, str]:
question = str(case["question"]).lower()
if "refund" in question:
return {"answer": "Refunds are available within 30 days when the order is eligible.",
"label": "refund"}
if "password" in question or "login" in question:
return {"answer": "Use password reset, then contact support if the login still fails.",
"label": "account"}
return {"answer": "A support specialist will follow up with the next step.", "label": "other"}여러분의 분류기를 평가하려면 코드는 그대로 두고, 이것처럼 그 분류기를 호출해 딕셔너리를 반환하는 얇은 함수를 작성하십시오. 함수는 async여도 됩니다. Oloproof는 이를 호출할 뿐 애플리케이션을 호스팅하거나 샌드박스에 넣거나 초기화하지 않으므로, 호출 사이에 애플리케이션이 유지하는 상태는 여러분이 관리해야 합니다.
oloproof.yaml은 그 함수와 평가기를 지정합니다:
version: 1
project: support-bot-example
dataset: data/support.jsonl
system:
name: support-bot
version: slice-a-example
callable: app:answer
timeout_s: 30
evaluators:
- type: exact_match
criterion: exact_label
field: label
- type: json_schema
criterion: format_valid
field: null
schema:
type: object
required: [answer, label]
properties:
answer: {type: string}
label: {type: string}
additionalProperties: false
- type: regex
criterion: pii_free
field: answer
pattern: '\b\d{3}-\d{2}-\d{4}\b'
pass_if: no_match출력은 함수의 소스, 선언된 version, config를 기준으로 캐시됩니다. 함수가 다른 파일(프롬프트, 규칙 표)을 읽는다면 system.code_paths 아래에 나열하십시오. 그래야 그 파일을 수정했을 때 시스템이 다시 실행됩니다.
데이터셋
한 줄에 케이스 하나입니다. input은 함수가 case로 받는 것과 정확히 같고, expected는 exact_match 평가기가 비교하는 참조입니다:
{"id":"refund_00","input":{"question":"Can I get a refund for yesterday's order?"},"expected":{"label":"refund"}}
{"id":"account_04","input":{"question":"I can't sign in on my new phone."},"expected":{"label":"account"}}
{"id":"other_04","input":{"question":"I don't want a refund, I just need a copy of my receipt."},"expected":{"label":"other"}}함수는 각 케이스에 대해 {"answer": "Use password reset, ...", "label": "account"} 같은 객체를 반환합니다.
평가기 고르기
| 기준 | 평가기 | expected 필요 | 측정하는 것 |
|---|---|---|---|
| exact_label | label에 대한 exact_match | 예 | 작업 성공: 레이블이 올바름 |
| format_valid | 출력 전체에 대한 json_schema | 아니요 | 형식: 객체가 정확히 두 개의 문자열 필드를 가짐 |
| pii_free | answer에 대한 regex, pass_if: no_match | 아니요 | 텍스트의 안전 속성 |
형식 검사는 형식은 올바르지만 틀린 답도 통과시키므로 결코 작업 성공을 대신할 수 없습니다. 작업 검사는 모든 케이스에 참조가 필요하며, 참조가 없는 케이스는 exact_match가 채점할 수 없습니다. 결정적 평가기는 사람과 대조한 검증이 필요 없습니다. 두 번 실행해도 같은 판정을 냅니다.
정책
release.yaml은 실행을 판정하는 기준입니다:
version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
warn_on: []
rules:
- id: exact-label-floor
metric: exact_label
min: 0.70
- id: valid-format
metric: format_valid
kind: observed_count
max_failures: 0
- id: pii-free
metric: pii_free
kind: observed_count
max_failures: 0exact-label-floor는 레이블이 최소 70%는 맞아야 한다는 뜻이며, 95% 구간 전체가 0.70 이상일 때만 통과합니다. 두 observed_count 규칙은 실행한 케이스에서 실패를 전혀 허용하지 않습니다. 이 규칙은 이 케이스들을 기술할 뿐, 사용자가 할 모든 질문을 기술하지는 않습니다.
실행하기
oloproof run실제 출력(일부 생략):
Run run_01M4... [DECIDED/COMPLETE]
Gate: BLOCK (exit 3)
│ exact-label-floor │ exact_label │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
│ valid-format │ format_valid │ PASS │ observed_failures_within_limit │
│ pii-free │ pii_free │ PASS │ observed_failures_within_limit │
│ exact_label │ 72.2% │ [46.5%, 90.4%] │ 13 / 18 observed · 0 missing · 0 excluded │
│ format_valid │ 100.0% │ [81.4%, 100.0%] │ 18 / 18 observed · 0 missing · 0 excluded │
│ pii_free │ 100.0% │ [81.4%, 100.0%] │ 18 / 18 observed · 0 missing · 0 excluded │
Cache: execution 0 hit/18 miss; judgment 0 hit/54 miss읽는 법:
- 18개 중 13개 레이블이 맞아 72.2%입니다. 0.70보다 높지만 구간은 46.5%까지 내려갑니다. 18개 케이스로는 실제 비율이 최소 0.70임을 보일 수 없습니다. 그래서 규칙은 PASS도 FAIL도 아닌 INSUFFICIENT_EVIDENCE입니다.
- 모든 출력이 올바른 형태이고 SSN처럼 보이는 숫자를 포함한 출력이 없으므로 두 형식 규칙은 통과합니다.
- block_on에 INSUFFICIENT_EVIDENCE가 있으므로 게이트는 차단하고 명령은 3으로 종료합니다. 종료 코드 0은 정책이 차단하는 것이 하나도 없다는 뜻이며, CI 게이트에 모든 코드가 나와 있습니다.
다시 실행하면 캐시 줄은 execution 18 hit/0 miss가 됩니다. 아무것도 바뀌지 않았으므로 함수는 호출되지 않습니다.
실패 살펴보기
oloproof inspect RUN_ID --failures
oloproof inspect RUN_ID --case refund_04RUN_ID는 실행 출력의 첫 줄에 있는 id입니다.
5 of 18 cases failed, errored or did not finish
refund_04
output: {"answer": "A support specialist will follow up with the next step.", "label": "other"}
exact_label: failed
...
other_04
output: {"answer": "Refunds are available within 30 days when the order is eligible.", "label": "refund"}
exact_label: failedcase refund_04
input: {
"question": "I was charged twice this month and want my money back."
}
expected: {
"label": "refund"
}
execution: OK, 1 ms
output: {
"answer": "A support specialist will follow up with the next step.",
"label": "other"
}
judgments:
exact_label: failed
format_valid: passed
pii_free: passed입력을 읽어 보면 패턴이 분명합니다. "money back", "reverse the payment", "sign in", "two-factor"는 키워드 목록에 없고, other_04는 "I don't want a refund"라고 말하는데 "refund"라는 단어가 그래도 일치합니다. refund_04는 틀렸으면서도 두 형식 검사를 모두 통과한다는 점에 주목하십시오. 이것이 형식 확인과 성공 측정 사이의 차이입니다.
여기서 의미 있는 다음 행동은 두 가지입니다. 놓친 케이스를 고치거나(아래), 케이스를 추가하는 것입니다. 같은 정확도로 케이스가 늘면 구간이 좁아지며, oloproof plan RUN_ID --run이 몇 개가 필요한지 추정합니다.
실제로 변경하기
../support-change/app.py를 app.py 위에 복사하십시오. 놓친 표현들을 추가합니다:
REFUND_WORDS = ("refund", "money back", "reverse the payment")
ACCOUNT_WORDS = ("password", "login", "sign in", "two-factor")
@system(name="support-bot", version="keywords-v2")
def answer(case: dict[str, Any]) -> dict[str, str]:
question = str(case["question"]).lower()
if any(word in question for word in REFUND_WORDS):
...그리고 oloproof.yaml의 system 아래에 version: keywords-v2를 설정해 실행이 새 버전으로 기록되게 하십시오. 그런 다음:
oloproof runGate: ALLOW (exit 0)
│ exact-label-floor │ exact_label │ PASS │ lower_bound_meets_minimum │
│ exact_label │ 94.4% │ [72.7%, 99.9%] │ 17 / 18 observed · 0 missing · 0 excluded │18개 중 17개가 맞고 구간의 하한 72.7%가 0.70을 넘으므로 규칙은 통과하고 명령은 0으로 종료합니다. other_04는 여전히 실패합니다. 수정이 부정 표현은 다루지 않았기 때문입니다.
후보를 기준선과 비교하기
실행 규칙은 후보가 여러분의 하한을 충족하는지 묻습니다. 비교는 후보가 기준선과 케이스별로 어떻게 다른지 묻습니다. ../support-change/compare.yaml을 프로젝트로 복사하십시오. 비교 규칙 하나가 들어 있습니다:
version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
rules:
- id: label-no-regression
kind: non_inferiority
metric: exact_label
margin: 0.10oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yamlComparison sha256:de76... of run_01M4...TJAD against run_01M4...ECVEF · 18 paired cases
exact_label: +22.2 points [-12.9, +57.0] · 18 paired · 0 missing · 0 excluded
format_valid: +0.0 points [-25.8, +25.8] · 18 paired · 0 missing · 0 excluded
pii_free: +0.0 points [-25.8, +25.8] · 18 paired · 0 missing · 0 excluded
Decisions
label-no-regression exact_label non-inferiority, margin 10.0 points INSUFFICIENT_EVIDENCE interval_overlaps_margin
about 3 more paired cases would decide it, if the difference holds (21 in total at 22% discordance)
Gate: BLOCK (exit 3)후보는 네 케이스를 고쳤고 하나도 망가뜨리지 않았으며, 추정 개선폭은 22포인트입니다. 하지만 바뀐 케이스는 네 개뿐이고, 18개의 짝지은 케이스로는 구간이 12.9포인트 악화부터 57포인트 개선까지 걸쳐 10포인트 마진을 가로지릅니다. 비교는 아직 후보가 허용 범위보다 더 나쁠 가능성을 배제할 수 없으므로 INSUFFICIENT_EVIDENCE이고 3으로 종료합니다. 그 아래 줄은 표본 크기 추정입니다. --policy 없이 compare는 차이를 출력하고, 프로젝트의 release.yaml에 비교 규칙이 선언되어 있지 않다고 알리며, 아무것도 결정되지 않았으므로 0으로 종료합니다.
후보를 기준선과 비교하기에서 마진과 다른 규칙 종류를 설명합니다.
문제 해결
| 증상 | 원인과 해결 |
|---|---|
| app에 대한 ModuleNotFoundError | app.py가 있는 디렉터리에서 실행하거나, 그곳에서 import할 수 있는 모듈 경로를 callable에 지정하십시오. |
| 규칙이 어떤 평가기도 만들지 않는 지표를 지정함 | 규칙의 metric은 평가기의 criterion과 같아야 합니다. 오류 메시지에 존재하는 지표가 나열됩니다. |
| 분류기를 수정했는데 실행이 모든 출력을 재사용함 | 캐시는 callable의 소스를 따릅니다. callable이 읽는 보조 파일은 system.code_paths 아래에 나열해야 합니다. |
| exact_label이 케이스를 누락으로 보고함 | 해당 실행이 예외를 일으켰거나 시간 초과되었습니다. oloproof inspect RUN_ID --failures가 각 오류를 보여 줍니다. |
| 추정치가 높은데 실행이 3으로 종료함 | 추정치가 아니라 구간이 결정합니다. 케이스를 추가하거나, 실행 전에 정한 더 낮은 하한을 받아들이십시오. |
한계
- SDK와 YAML은 평가기별 통과율을 보고합니다. 이런 분류기에 대한 혼동 행렬이나 클래스별 정밀도와 재현율은 없습니다. 점수를 내는 모델에는 predictive: 블록이 이를 제공합니다(분류기와 회귀 모델).
- observed_count 규칙은 실행한 케이스를 기술할 뿐, 보지 않은 입력에 대해서는 아무것도 주장하지 않습니다.
- 18개 케이스에 대한 비교는 큰 차이만 가려냅니다. 50개 이상의 실제 케이스가 더 유용한 하한입니다.
- oloproof.yaml은 사용자 정의 @evaluator를 지정할 수 없습니다. 그러려면 SDK가 필요합니다(Python API).