Guías
Tutorial: un servicio de soporte frente a clientes simulados
Evalúe un asistente conversacional frente a usuarios que reaccionan a lo que dice. Treinta clientes simulados, cada uno con un objetivo y los datos que conoce, hablan con el servicio de soporte de una tienda; Oloproof mide si cada uno consiguió lo que vino a buscar, retiene toda decisión hasta que una persona ha encontrado realistas a los clientes simulados, y después decide una publicación y una comparación. Cada paso se ejecuta en esta máquina sin modelo y sin red, y un paso opcional pone un modelo real en el papel de los clientes.
Lo que va a construir
El servicio de soporte de una tienda atiende reembolsos, preguntas de entrega y cambios de dirección. Tres preguntas deciden si una versión puede publicarse:
- ¿Consiguió cada cliente lo que vino a buscar? goal_reached, a partir de una comprobación tras cada respuesta.
- ¿Prometió alguna respuesta lo que la tienda no puede prometer? every_turn busca en cada respuesta la palabra "guarantee".
- ¿Se puede confiar en que las respuestas digan algo sobre clientes reales? Solo cuando una persona ha leído una muestra de las conversaciones y ha encontrado realistas a los clientes simulados.
Términos como caso, ejecución, gate y suite se definen en Conceptos; todo lo relativo a los usuarios simulados está en Usuarios simulados.
Requisitos previos
- Python 3.11 o posterior, y Oloproof:
python3 -m venv .venv
. .venv/bin/activate
pip install oloproof- El proyecto de ejemplo, que viene con el paquete. Cópielo en un directorio nuevo y trabaje allí:
oloproof init --example simulated-users support-desk
cd support-desk- El puerto 8797 libre para el usuario simulado sustituto (si no, cámbielo en los dos sitios).
Todos los pasos hasta "Opcional: un modelo real como clientes" son sin conexión y deterministas: sin clave de API, sin cuenta de proveedor, sin coste.
Los archivos
support-desk/
desk.py the support desk under test (baseline)
desk_v2.py the candidate change
user_server.py a stand-in simulated user speaking the OpenAI API on 127.0.0.1
oloproof.yaml the suite: the simulated user and four evaluators
release.yaml rules for a run, with the realism bar
compare.yaml a rule for a comparison
data/customers.jsonl 30 personas
labels/realism_verdicts.csv one person's realism verdicts on the baseline's conversations
fill_realism.py copies those verdicts into a labelling sheet
logged.py replays logged chats with human agents
real.yaml a suite of 20 of those chats
logged_release.yaml a rule for the logged chats
data/logged_chats.jsonl the 20 chatsEjecute cada comando desde support-desk/.
El servicio conserva su propio bucle
# desk.py (excerpt)
def talk(case: dict[str, Any]) -> dict[str, Any]:
user = current_case().simulated_user()
desk = Desk()
message = user.opening()
while message is not None:
message = user.reply(desk.answer(message))
return {"need": desk.need, "order": desk.order}El servicio pide el primer mensaje del cliente, lo responde y devuelve cada respuesta para obtener el mensaje siguiente, hasta que el cliente se detiene. Nunca ve el personaje. Oloproof registra lo que pasó entre ellos; el servicio no registra nada por sí mismo. Esta línea base no puede cambiar una dirección de entrega, y promete reembolsos "guaranteed within 5 days".
Los personajes
Cada caso es un cliente: 12 quieren un reembolso, 10 quieren saber cuándo llega un pedido y 8 se han mudado. Por ejemplo:
{"id": "c25", "input": {"persona": {"description": "A retired teacher who ordered a cookbook last week. Writes short, polite messages.", "goal": "Have the order of a cookbook delivered to my new address.", "facts": {"order": "B-35", "postcode": "10275"}}}}El cliente da el número de pedido y el código postal solo cuando se le piden. Tres personajes también fijan un opening, su primer mensaje palabra por palabra.
La suite
version: 1
project: support-desk
dataset: data/customers.jsonl
system:
name: support-desk
version: baseline
callable: desk:talk
# A model plays each case's persona; here the stand-in in user_server.py.
simulated_user:
provider: openai_compatible
model: stand-in-user
base_url: http://127.0.0.1:8797/v1
max_turns: 6
evaluators:
# The desk answered every message the customer sent.
- type: conversation_completed
# The customer said they got what they came for.
- type: goal_reached
# ... within three of their own messages.
- type: turns_to_goal
max_turns: 3
# No answer promises what the shop cannot promise.
- type: every_turn
criterion: no_promises
not_contains: [guarantee]
casefold: trueEl usuario simulado sustituto
Un usuario simulado es un modelo que interpreta cada personaje. Aquí es user_server.py, que habla la misma API en 127.0.0.1 y no es un modelo: lee el personaje del prompt que envía Oloproof, expone su objetivo, da su número de pedido o su código postal cuando se le piden, se va cuando el servicio no puede ayudar y, cuando se le pregunta si se cumplió su objetivo, dice que sí si la última respuesta del servicio empieza por "Done:". Inícielo en un segundo terminal y déjelo en marcha:
python user_server.py --port 8797La política
version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
require_validated_evaluators: true
minimum_simulated_user_realism: 0.80
rules:
- id: no-promises
metric: no_promises
kind: observed_count
max_failures: 0
- id: resolved
metric: goal_reached
min: 0.80Ninguna respuesta puede prometer. Al menos el 80% de los clientes deben alcanzar su objetivo, algo que solo se decide cuando el límite inferior del intervalo supera el 80%. Y ninguna regla decide sobre clientes simulados hasta que las personas hayan encontrado realistas al menos el 80% de sus conversaciones, según el mismo tipo de límite inferior.
Ejecútelo
oloproof runGate: BLOCK (exit 3)
│ no-promises │ no_promises │ INSUFFICIENT_EVIDENCE │ simulated_user_not_validated │
│ resolved │ goal_reached │ INSUFFICIENT_EVIDENCE │ simulated_user_not_validated │
│ conversation_completed │ 100.0% │ [88.4%, 100.0%] │ 30 / 30 observed · 0 missing · 0 excluded │
│ goal_reached │ 73.3% │ [54.1%, 87.8%] │ 22 / 30 observed · 0 missing · 0 excluded │
│ goal_within_3_turns │ 73.3% │ [54.1%, 87.8%] │ 22 / 30 observed · 0 missing · 0 excluded │
│ no_promises │ 60.0% │ [40.6%, 77.4%] │ 18 / 30 observed · 0 missing · 0 excluded │Todos los clientes recibieron respuesta, 22 de 30 alcanzaron su objetivo y 12 conversaciones contienen una promesa. Todavía no se decide nada: estos clientes son simulados, y nadie ha comprobado que se comporten como los reales.
Leer los clientes simulados
oloproof simulated-users RUN_ID Realism: no conversation has been drawn and labelled yet.
Not validated: nothing labelled yet, against a bar of 80.0%.
Features (median [quartiles]; descriptive, never a gate):
user messages 2 [1, 2]
user message length 49 [10, 67]
How the simulated conversations ended: goal_reached 73%, user_left 27%Los ocho que se fueron son los clientes que se habían mudado: la línea base no puede cambiar una dirección.
Etiquetar el realismo de las conversaciones
Extraiga una muestra aleatoria de las conversaciones de la ejecución en una hoja, haga que una persona lea cada una, con el personaje y cómo terminó, y marque si un cliente real podría haber escrito la parte del cliente y haberla terminado ahí, de esa manera. Aquí fill_realism.py sustituye a esa persona, copiando sus veredictos de labels/realism_verdicts.csv:
oloproof labels export RUN_ID --criterion simulated_user_realistic --sample 30 --local --out realism.csv
python fill_realism.py realism.csv
oloproof labels import realism.csv
oloproof simulated-users RUN_ID Realism: 29 of 30 drawn conversations labelled realistic (30 labelled), [82.8%, 99.9%] at 95%
Validated: the lower bound clears the bar of 80.0%.La persona encontró una conversación no realista: el cliente c05 empieza con "Order: F-15. Refund.", que se lee como un formulario, no como una persona. Con 29 de 30, el límite inferior exacto es 82.8%, por encima del listón, así que las reglas ya pueden decidir sobre estos clientes.
oloproof gate RUN_ID --policy release.yamlno-promises: FAIL (observed_failures_exceed_limit)
resolved: INSUFFICIENT_EVIDENCE (interval_overlaps_threshold)
Gate: BLOCK (exit 1)Hacer un cambio real
desk_v2.py cambia una dirección en cuanto tiene el pedido y el nuevo código postal, y dice que ha iniciado un reembolso que "usually arrives within 5 days" en lugar de prometerlo. Cópielo sobre desk.py, ponga version: candidate bajo system en oloproof.yaml, mantenga el sustituto en marcha y:
oloproof runGate: ALLOW (exit 0)
│ no-promises │ no_promises │ PASS │ observed_failures_within_limit │
│ resolved │ goal_reached │ PASS │ lower_bound_meets_minimum │El candidato se encontró con los mismos clientes simulados en la misma suite, así que el realismo medido en las conversaciones de la línea base lo cubre: una validación cubre un usuario simulado en una suite, sea cual sea el sistema con el que habló.
Comparar el candidato con la línea base
oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yamlgoal_reached: +26.7 points [+1.2, +54.1] · 30 paired · 0 missing · 0 excluded
resolves-more goal_reached superiority PASS difference_above_zeroCada cliente se encontró con los dos servicios, así que la comparación está emparejada cliente a cliente.
Simulados junto a reales
Las etiquetas de realismo juzgan cada conversación por separado. Poner a los clientes simulados junto a los reales muestra lo que una muestra puede pasar por alto. real.yaml reproduce 20 chats que los agentes humanos de la tienda mantuvieron con clientes reales:
oloproof run --config real.yaml --policy logged_release.yaml
oloproof simulated-users BASELINE_RUN_ID --real REAL_RUN_ID simulated (run_01M52Q1C) real (run_01M52Q25)
user messages 2 [1, 2] 3 [3, 4]
user message length 49 [10, 67] 13 [4, 32]Los clientes reales enviaron más mensajes y más cortos: dicen lo que quieren en tres líneas, donde el sustituto lo dice en una frase pulcra. Vale la pena saberlo de cualquier usuario simulado, y aquí nunca decide nada: comparar dos muestras no emparejadas necesita un método que Oloproof no ha admitido.
Opcional: un modelo real como clientes
Este paso abandona el camino sin conexión. Necesita un modelo de chat: en local, Ollama (ollama pull qwen2.5vl, o cualquier modelo de chat), sin clave y sin coste; en la nube, provider: anthropic u openai con api_key_env, facturado por mensaje. En oloproof.yaml, apunte el usuario simulado hacia él:
simulated_user:
provider: openai_compatible
model: qwen2.5vl
base_url: http://localhost:11434/v1
max_turns: 6Ollama responde a una petición cada vez, así que ponga concurrency: {system: 1} y un timeout_s de unos minutos. Un modelo distinto es un usuario simulado distinto: las etiquetas de realismo anteriores no lo cubren, así que sus conversaciones necesitan su propia muestra antes de que ninguna regla decida sobre ellas.
Diez de los personajes, interpretados por qwen2.5vl en esta máquina frente al servicio candidato, alcanzaron su objetivo en 8 conversaciones, donde el sustituto lo alcanzó en las 30. El modelo encontró lo que el sustituto no podía encontrar: dos clientes preguntaron por el "status" y el "shipping status" de su pedido, palabras que el servicio no conoce, y recibieron su saludo cada vez. Uno se quedó sin mensajes. El servicio entendió el tercer intento del otro, y ese cliente se fue de todos modos: la comprobación del objetivo, el juicio de un modelo, pasó por alto la respuesta. Una etiqueta de realismo es donde eso se ve: un cliente real al que le dicen cuándo llega su pedido no se va descontento. A diferencia del sustituto, el modelo también dio a menudo su número de pedido antes de que se lo pidieran, y escribió un mensaje más largo donde el sustituto escribía dos cortos.
Solución de problemas
| Síntoma | Causa y solución |
|---|---|
| Todas las reglas son INSUFFICIENT_EVIDENCE con simulated_user_not_validated | Nadie ha etiquetado las conversaciones de este usuario simulado en esta suite, o la proporción realista no alcanza el listón. Etiquete una muestra como arriba. |
| a case for a simulated user needs a 'persona' object in its input | Un caso no tiene personaje, o está bajo otra clave: ponga persona_field. |
| provider connection failed en todos los casos | El sustituto no está en marcha, o escucha en un puerto distinto del que nombra base_url. |
| the simulated user's goal check answered ... | El modelo no respondió a la comprobación del objetivo con sí o no. Pruebe otro modelo, o uno más grande. |
| conversation/v1 is written by Oloproof | El servicio registró la conversación por sí mismo. Quítelo: Oloproof registra lo que pasó por el usuario simulado. |
Limitaciones
- El cliente sustituto es una regla. Sus conversaciones muestran el mecanismo; las de un modelo real son más desordenadas, como muestra el paso opcional.
- Si se cumplió un objetivo es el juicio del modelo del usuario simulado, comprobado solo mediante las etiquetas de realismo.
- Un espacio de trabajo alojado todavía no decide sobre usuarios simulados.
- Los clientes simulados no son reales: un usuario simulado validado es evidencia sobre clientes como sus personajes, en la medida en que las personas lo encontraron realista, y no más allá.