Guías
Tutorial: un clasificador o una salida estructurada
Evalúe una función de Python que etiqueta preguntas de soporte, lea por qué se bloquea la publicación, corrija los errores y compare la corrección con el original, todo en su propia máquina, sin cuenta, sin red y sin modelo.
Lo que va a construir
Un bot de soporte que devuelve un objeto JSON con un answer y un label (refund, account u other). Lo someterá a tres requisitos: la etiqueta acierta con suficiente frecuencia, la salida siempre tiene la forma correcta, y ninguna respuesta filtra nada que parezca un número de la seguridad social de EE. UU. Dos de ellos son comprobaciones de formato que no necesitan respuesta de referencia; uno mide el éxito de la tarea frente a una etiqueta de referencia. La diferencia importa, y esta página los mantiene separados.
Los términos usados abajo (caso, ejecución, métrica, intervalo, regla, gate) se definen en Conceptos.
Requisitos previos
- Python 3.11 o posterior.
- Oloproof, instalado en un entorno virtual:
python3 -m venv .venv
. .venv/bin/activate
pip install oloproof- El proyecto de ejemplo y el cambio candidato, que vienen con el paquete. Copie ambos en directorios nuevos y trabaje en el primero; cada archivo también aparece abajo, así que puede escribirlos a mano:
oloproof init --example support_bot support-classifier
oloproof init --example classification support-change
cd support-classifierEn esta página no se usa ninguna clave de API, cuenta de proveedor ni acceso a la red.
Los archivos
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 hereEjecute cada comando desde el directorio support-classifier/. Oloproof guarda allí su almacén en .oloproof/; borre ese directorio para empezar de cero.
La aplicación y su adaptador
Se llega a su aplicación a través de un adaptador. Para una aplicación de Python, el adaptador es la propia función: Oloproof la importa, la llama una vez por caso con el input del caso y registra el diccionario que devuelve como salida de ese caso.
# 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"}Para evaluar su propio clasificador, deje su código donde está y escriba una función delgada como esta que lo llame y devuelva un diccionario. La función puede ser async. Oloproof la llama; no aloja, aísla ni reinicia su aplicación, así que cualquier estado que su aplicación conserve entre llamadas lo gestiona usted.
oloproof.yaml nombra esa función y los evaluadores:
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_matchLas salidas se almacenan en caché según el código fuente de la función, la version declarada y config. Si la función lee otros archivos (un prompt, una tabla de reglas), enumérelos en system.code_paths, para que editarlos vuelva a ejecutar el sistema.
El conjunto de datos
Un caso por línea. input es exactamente lo que su función recibe como case; expected es la referencia con la que compara el evaluador 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"}}La función devuelve, para cada caso, un objeto como {"answer": "Use password reset, ...", "label": "account"}.
Elegir los evaluadores
| Criterio | Evaluador | Necesita expected | Qué mide |
|---|---|---|---|
| exact_label | exact_match sobre label | sí | éxito de la tarea: la etiqueta es la correcta |
| format_valid | json_schema sobre toda la salida | no | formato: el objeto tiene exactamente los dos campos de texto |
| pii_free | regex sobre answer, pass_if: no_match | no | una propiedad de seguridad del texto |
Una comprobación de formato aprueba una respuesta incorrecta bien formada, así que nunca puede sustituir al éxito de la tarea. Una comprobación de tarea necesita una referencia para cada caso; donde un caso no la tiene, exact_match no puede puntuarlo. Los evaluadores deterministas no necesitan validación frente a personas: ejecutar uno dos veces da el mismo veredicto.
La política
release.yaml es aquello contra lo que se decide la ejecución:
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 dice que la etiqueta debe acertar al menos el 70% de las veces, y solo pasa cuando todo el intervalo al 95% está en 0.70 o por encima. Las dos reglas observed_count no permiten ningún fallo en los casos que ejecutó; describen estos casos, no todas las preguntas que harán los usuarios.
Ejecútelo
oloproof runSalida real, recortada:
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 missCómo leerla:
- 13 de 18 etiquetas son correctas, un 72.2%. Eso está por encima de 0.70, pero el intervalo baja hasta el 46.5%: 18 casos no pueden demostrar que la tasa real sea al menos 0.70. Así que la regla queda en INSUFFICIENT_EVIDENCE, ni PASS ni FAIL.
- Todas las salidas tienen la forma correcta y ninguna contiene un número parecido a un SSN, así que ambas reglas de formato pasan.
- block_on incluye INSUFFICIENT_EVIDENCE, así que el gate bloquea y el comando termina con 3. Terminar con 0 significaría que no ocurrió nada sobre lo que la política bloquea; Gates en CI enumera todos los códigos.
Vuelva a ejecutarlo y la línea de caché dice execution 18 hit/0 miss: nada cambió, así que no se llama a la función.
Inspeccionar los fallos
oloproof inspect RUN_ID --failures
oloproof inspect RUN_ID --case refund_04RUN_ID es el identificador de la primera línea de la salida de la ejecución.
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: passedEl patrón es evidente en cuanto se leen las entradas: "money back", "reverse the payment", "sign in" y "two-factor" no están en las listas de palabras clave, y other_04 dice "I don't want a refund", que la palabra "refund" captura de todos modos. Observe que refund_04 pasa ambas comprobaciones de formato aun siendo incorrecto: esa es la distancia entre comprobar el formato y medir el éxito.
Aquí tienen sentido dos acciones siguientes. Corregir los errores (abajo), o añadir casos: con más casos y la misma precisión el intervalo se estrecha, y oloproof plan RUN_ID --run estima cuántos.
Hacer un cambio real
Copie ../support-change/app.py sobre app.py. Añade las formulaciones que faltaban:
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):
...y ponga version: keywords-v2 bajo system en oloproof.yaml, para que la ejecución se registre como la nueva versión. Después:
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 │17 de 18 son correctos y el límite inferior del intervalo, 72.7%, supera 0.70, así que la regla pasa y el comando termina con 0. other_04 sigue fallando: la corrección no tocó la negación.
Comparar el candidato con la línea base
La regla de ejecución pregunta si el candidato alcanza su mínimo. Una comparación pregunta en qué se diferencia de la línea base, caso por caso. Copie ../support-change/compare.yaml en el proyecto; contiene una regla de comparación:
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)El candidato corrigió cuatro casos y no rompió ninguno, una ganancia estimada de 22 puntos. Pero solo cambiaron cuatro casos, y 18 casos emparejados dejan un intervalo que va de 12.9 puntos peor a 57 puntos mejor, que cruza el margen de 10 puntos. La comparación aún no puede descartar que el candidato sea peor en más de lo que usted acepta, así que queda en INSUFFICIENT_EVIDENCE y termina con 3. La línea de debajo es la estimación del tamaño necesario. Sin --policy, compare imprime las diferencias, indica que el release.yaml del proyecto no declara ninguna regla de comparación y termina con 0 porque no se decidió nada.
Comparar un candidato con una línea base explica el margen y los demás tipos de regla.
Solución de problemas
| Síntoma | Causa y solución |
|---|---|
| ModuleNotFoundError para app | Ejecute desde el directorio que contiene app.py, o dé a callable una ruta de módulo importable desde allí. |
| Una regla nombra una métrica que ningún evaluador produce | El metric de la regla debe ser igual al criterion de un evaluador; el error enumera las métricas que existen. |
| Editó el clasificador y la ejecución reutilizó todas las salidas | La caché sigue el código fuente del callable; un archivo auxiliar que lea debe figurar en system.code_paths. |
| exact_label informa de casos que faltan | Esas ejecuciones lanzaron una excepción o agotaron el tiempo; oloproof inspect RUN_ID --failures muestra cada error. |
| La ejecución termina con 3 con una estimación alta | Decide el intervalo, no la estimación. Añada casos o acepte un mínimo más bajo, decidido antes de la ejecución. |
Limitaciones
- El SDK y el YAML informan de tasas de aprobados por evaluador. No hay matriz de confusión ni precisión y exhaustividad por clase para un clasificador como este; un bloque predictive: las calcula para un modelo con puntuaciones (Modelos predictivos).
- Las reglas observed_count describen los casos que ejecutó; no afirman nada sobre entradas no vistas.
- Una comparación sobre 18 casos solo resuelve diferencias grandes. Cincuenta o más casos reales son un mínimo más útil.
- oloproof.yaml no puede nombrar un @evaluator personalizado; para eso hace falta el SDK (El SDK).