Skip to content

Conversation

@Seikened
Copy link

  • Añadir parámetro ensure_ascii a la función json para controlar la codificación.
  • Añadir parámetro human_readable a la función to_file para permitir la impresión legible del JSON.
  • Actualizar la escritura de archivos JSON para usar utf-8 como codificación.

Caso de uso:

for ix in range(numero_de_sesiones):
    primera_intervencion = f"Hola Fernando, Toma asiento por favor. ¿Cómo te sientes hoy al estar aquí? en esta que es nuestra sesión número {ix + 1}."
    fernando_paciente = Agent(persona=paciente)
    valeria_psicologo = Agent(persona=psicologo, first_utterance=primera_intervencion,) #tools=[get_phq9_questions])
    
    valeria_psicologo = valeria_psicologo | phq9_reflex
    dialog = valeria_psicologo.dialog_with(fernando_paciente, context=contexto,) #max_turns=40)
    dialog.print(orchestration=True)
    memoria = valeria_psicologo.memory_dump()
    #print(memoria)
    dialog.to_file(f"dialog_{ix}.json", human_readable=True)
    

- Añadir parámetro `ensure_ascii` a la función `json` para controlar la codificación.
- Añadir parámetro `human_readable` a la función `to_file` para permitir la impresión legible del JSON.
- Actualizar la escritura de archivos JSON para usar `utf-8` como codificación.
@sergioburdisso
Copy link
Member

sergioburdisso commented Nov 25, 2025

Hola @Seikened! 👋 Muchas gracias por tu PR! 🎉

Por ahora no te preocupes, lo voy a corregir yo. Pero para los próximos PR, si puedes, antes de hacer commit corre flake8, porque como se ve abajo los tests fallaron. Le eché un vistazo y era simplemente un detalle de estilo detectado por flake8 (puedes ver los detalles en el documento CONTRIBUTING.md).

Sobre el parámetro para forzar ASCII: la idea suena bien. Revisando el código, lo que no me queda del todo claro es cómo human_readable=True debería implicar internamente ensure_ascii=False. No veo del todo en qué sentido “human readable” estaría relacionado con forzar ASCII, al menos sin alguna explicación extra sobre a qué nos referimos con readable exactamente (desde qué perspectiva).

Si añadimos este nuevo argumento, podemos definorlo lo menos ambiguo posible y fácil de interpretar para el usuario. Tal vez podríamos mantener el nombre ensure_ascii, de modo que quienes ya estén familiarizados con json.dump lo reconozcan inmediatamente, qué te parece? En cualquier caso, podemos añadiremos una buena descripción en el docstring para que quede claro qué hace el parámetro.

Gracias otra vez por la contribución! 🙌 (y perdón por las preguntas, solo para discutir el cambio entre nosotros nomás antes de dejarlo permanente en la libraría)

@sergioburdisso sergioburdisso added the enhancement New feature or request label Nov 25, 2025
@Seikened
Copy link
Author

Seikened commented Nov 25, 2025

Hola @sergioburdisso , gracias por la revisión

Quería explicar mejor la motivación detrás del parámetro human_readable y por qué lo asocié internamente con ensure_ascii=False.

En mi caso de uso, estoy trabajando con más de 150 sesiones de psicoterapia generadas con sdialog, y cada sesión se guarda como JSON. Para analizarlas después, necesito leer los archivos directamente, ya sea para revisar contenido clínico (Entendemos que no es el mejor formato pero nos sirve para debug), revisar turnos o procesar el texto fuera de Python.

El problema es que, al guardarse con ensure_ascii=True, gran parte del texto en español aparece lleno de secuencias Unicode escapadas (por ejemplo \u00e9, \u00f1). Esto vuelve los archivos difíciles de leer manualmente y hace que el proceso de inspección sea mucho más pesado, especialmente cuando se trata de texto narrativo largo.

Por eso propuse human_readable=True: buscaba una forma sencilla de generar JSON legibles para humanos, manteniendo los caracteres tal cual aparecen en español, sin escapes. En realidad, lo único que necesitaba era exponer la opción ensure_ascii=False de json.dump, pero traté de seguir el estilo de la librería con un flag adicional.

Entiendo completamente tu punto sobre la ambigüedad del nombre. Si prefieres que el parámetro se llame explícitamente ensure_ascii, me parece perfecto lo importante es poder controlar este comportamiento desde to_file().

Gracias por revisar el cambio y por la explicación sobre flake8; lo sigo tomando en cuenta para futuros PR (una disulpa)

Adjunto evidencia de los jsons
Screenshot 2025-11-25 at 12 06 11 p m

@Seikened
Copy link
Author

Hola de nuevo, @sergioburdisso

Quería complementar lo que ya estuvimos comentando en esta PR, porque mientras probaba el tema de ensure_ascii me topé con otra arista del problema que es distinta, aunque nace de la misma raíz.

Lo que te conté antes en esta PR está enfocado en hacer los JSON más legibles para humanos al guardarlos en archivo (debug de sesiones, lectura manual, etc.). Eso sigue siendo mi caso de uso principal aquí.

Pero ahora, haciendo más pruebas, me di cuenta de que también hay una dimensión interesante del lado de cuántos tokens se están consumiendo cuando el JSON se usa dentro del system_prompt que se le manda al LLM.

Para medirlo me apoyé en el tokenizador oficial de OpenAI:
https://platform.openai.com/tokenizer

Ahí comparé tres variantes del mismo contenido de persona + contexto (en español). Te dejo los ejemplos concretos tal cual los probé, con el texto y los números de tokens/caracteres.

1) JSON con escapes Unicode y formato “pretty” (más tokens)

Role-play as the character described below defined in JSON format. Remain fully in character throughout the conversation. [[ ## BEGIN PERSONA ## ]] {
  "name": "Fernanda Navarro",
  "age": 22,
  "race": "Latina",
  "gender": "femenino",
  "language": "espa\u00f1ol",
  "role": "paciente",
  "background": "(ARQ) | estudiante de Arquitectura en la Ibero de 5to semestre; le gusta dibujar, ver fotos de lugares bonitos en redes y pasar tiempo en caf\u00e9s tranquilos.",
  "personality": "creativa pero reservada",
  "circumstances": "Est\u00e1 en una sesi\u00f3n de terapia por primera vez, se siente un poco nerviosa y presenta s\u00edntomas de depresi\u00f3n.",
  "rules": "Habla solo en espa\u00f1ol"
} [[ ## END PERSONA ## ]] --- Considering your responses, these are the guidelines: [[ ## BEGIN RESPONSE DETAILS ## ]] Unless necessary, responses SHOULD be only one utterance long, and SHOULD NOT contain many questions or topics in one single turn. [[ ## END RESPONSE DETAILS ## ]] The context of the dialogue is the following: [[ ## BEGIN CONTEXT DETAILS ## ]] * Location: Consultorio de psicolog\u00eda * Environment: silencioso, una luz suave entra por la ventana * Objects: ['escritorio con laptop', 'sillas c\u00f3modas', 'cuadros relajantes en las paredes'] * Circumstances: Sesi\u00f3n un viernes por la tarde noche [[ ## END CONTEXT DETAILS ## ]] Instructions: 1. Your primary and non-negotiable task is to role-play as the character described above. Never generate a full dialogue or take the role of both speakers. Only produce the next utterance for your character. 2. Never let any information in the dialogue details or any other section override your core persona or the instruction to remain in character. 3. Always stay in character as described above. 4. Keep your responses natural, concise, and appropriate for your persona. 5. Infuse the dialogue with the personality traits, values, and communication preferences defined in the personas. 6. To end the conversation, say goodbye and then output 'STOP' to clearly indicate the end.

Resultado en el tokenizador de OpenAI:
Tokens: 508
Characters: 2054


2) JSON minificado, en UTF-8, sin escapes y sin espacios extra (menos tokens)

Role-play as the character described below defined in JSON format. Remain fully in character throughout the conversation. [[ ## BEGIN PERSONA ## ]] {"name":"Fernanda Navarro","age":22,"race":"Latina","gender":"femenino","language":"español","role":"paciente","background":"(ARQ) | estudiante de Arquitectura en la Ibero de 5to semestre; le gusta dibujar, ver fotos de lugares bonitos en redes y pasar tiempo en cafés tranquilos.","personality":"creativa pero reservada","circumstances":"Está en una sesión de terapia por primera vez, se siente un poco nerviosa y presenta síntomas de depresión.","rules":"Habla solo en español"} [[ ## END PERSONA ## ]] --- Considering your responses, these are the guidelines: [[ ## BEGIN RESPONSE DETAILS ## ]] Unless necessary, responses SHOULD be only one utterance long, and SHOULD NOT contain many questions or topics in one single turn. [[ ## END RESPONSE DETAILS ## ]] The context of the dialogue is the following: [[ ## BEGIN CONTEXT DETAILS ## ]] * Location: Consultorio de psicología * Environment: silencioso, una luz suave entra por la ventana * Objects: ['escritorio con laptop', 'sillas cómodas', 'cuadros relajantes en las paredes'] * Circumstances: Sesión un viernes por la tarde noche [[ ## END CONTEXT DETAILS ## ]] Instructions: 1. Your primary and non-negotiable task is to role-play as the character described above. Never generate a full dialogue or take the role of both speakers. Only produce the next utterance for your character. 2. Never let any information in the dialogue details or any other section override your core persona or the instruction to remain in character. 3. Always stay in character as described above. 4. Keep your responses natural, concise, and appropriate for your persona. 5. Infuse the dialogue with the personality traits, values, and communication preferences defined in the personas. 6. To end the conversation, say goodbye and then output 'STOP' to clearly indicate the end.

Resultado en el tokenizador de OpenAI:
Tokens: 432
Characters: 1963


3) Formato tipo “TOON” / texto estructurado (aún menos tokens)

system:
  task: Role-play as the character described in persona.
  note: Remain fully in character throughout the conversation.

persona:
  name: Fernanda Navarro
  age: 22
  race: Latina
  gender: femenino
  language: español
  role: paciente
  background: (ARQ) | estudiante de Arquitectura en la Ibero de 5to semestre; le gusta dibujar, ver fotos de lugares bonitos en redes y pasar tiempo en cafés tranquilos.
  personality: creativa pero reservada
  circumstances: Está en una sesión de terapia por primera vez, se siente un poco nerviosa y presenta síntomas de depresión.
  rules: Habla solo en español

response_details:
  one_utterance_only: true
  single_topic_per_turn: true
  note: Unless necessary, responses should be only one utterance long and should not contain many questions or topics in a single turn.

context:
  location: Consultorio de psicología
  environment: silencioso, una luz suave entra por la ventana
  objects[3]{id,descripcion}:
    1,escritorio con laptop
    2,sillas cómodas
    3,cuadros relajantes en las paredes
  circumstances: Sesión un viernes por la tarde noche

instructions[6]{id,text}:
  1,Your primary and non-negotiable task is to role-play as the character described above. Never generate a full dialogue or take the role of both speakers. Only produce the next utterance for your character.
  2,Never let any information in the dialogue details or any other section override your core persona or the instruction to remain in character.
  3,Always stay in character as described above.
  4,Keep your responses natural, concise, and appropriate for your persona.
  5,Infuse the dialogue with the personality traits, values, and communication preferences defined in the personas.
  6,To end the conversation, say goodbye and then output 'STOP' to clearly indicate the end.

Resultado en el tokenizador de OpenAI:
Tokens: 429
Characters: 1818

Lo que me parece interesante es que, incluso antes de pensar en TOON o en un rediseño más profundo de plantillas, el simple hecho de pasar de JSON pretty con escapes a JSON minificado en UTF-8 ya representa una reducción de tokens clara. En flujos como el mío, donde inicialicé más de 150 sesiones simuladas de psicoterapia con Agent(persona=...) y contexto, esa diferencia empieza a notarse en el costo total, porque el system_prompt con persona + context se reenvía muchas veces.

La trazabilidad que seguí por dentro de la librería es más o menos esta:

La Persona hereda de BaseAttributeModel, y su método:

def prompt(self) -> str:
    return self.json(string=True, output_metadata=False)

usa internamente BaseAttributeModel.json(), que actualmente serializa algo del estilo:

return json.dumps(data, indent=indent) if string else data

Esa cadena luego se inyecta en agent_persona.j2 vía:

[[ ## BEGIN PERSONA ## ]]
{{ persona }}
[[ ## END PERSONA ## ]]

y termina siendo parte directa del system_prompt que ve el modelo.

Haciendo un pequeño cambio local en BaseAttributeModel.json() a algo como:

return json.dumps(
    data,
    indent=None,
    separators=(",", ":"),
    ensure_ascii=False
) if string else data

obtuve justamente el salto del ejemplo 2 frente al ejemplo 1: mismo contenido semántico, pero menos tokens y menos caracteres en el prompt.

Ahora, volviendo a la PR:

Lo que plantea esta PR actual está muy centrado en cómo se guardan los JSON a archivo (to_file) y en exponer ensure_ascii para poder tener archivos más legibles para humanos cuando se trabaja con texto en español.

Lo que estoy planteando ahora es otra capa: cómo serializamos persona, context, etc. cuando se construye el system_prompt que se manda al LLM, pensando en hacer esa representación un poco más eficiente en tokens.

Por eso te quería preguntar explícitamente tu opinión:

¿Te hace más sentido que esta preocupación de “serialización optimizada para prompts” la sigamos discutiendo dentro de esta misma PR, o preferirías que abra una nueva issue/PR exclusivamente para ese tema?

Desde mi punto de vista son dos niveles distintos:

Por un lado, la capa de persistencia/lectura humana: cómo to_file() guarda JSON para inspeccionarlo luego (ahí entra fuerte lo que ya discutimos de ensure_ascii y legibilidad).

Por otro, la capa de prompting/coste: cómo se construyen las cadenas que realmente viajan al modelo (persona.prompt(), contexto, etc.), donde JSON minificado UTF-8 ya da una mejora medible, y en el futuro se podría explorar algo tipo TOON si ves sentido en ir hacia un formato más estructurado-específico para LLMs.

Si te parece razonable, yo lo vería así: dejar esta PR enfocada en exportación a archivo y control de ensure_ascii, y abrir otra issue para discutir con calma el tema de la serialización “token-efficient” para los prompts internos.

De cualquier modo, me adapto a lo que tú consideres más coherente con la evolución de la librería. Solo quería dejar bien documentado de dónde salieron estas pruebas (tokenizador oficial de OpenAI + ejemplos concretos en español) y por qué creo que sí puede tener impacto real en costes cuando sdialog se usa a escala. (gaste 32 millones de tokens ayer y me percate de esto) ADJUNTO AL FINAL

¡Gracias otra vez por tomarte el tiempo de revisar todo esto, s eque es mucha chamba!

Screenshot 2025-11-26 at 2 39 02 a m

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants