Resumen de reuniones con acciones y responsables
Convierte la transcripción de una reunión en un JSON con resumen, decisiones y acciones con responsable, fecha y la cita que las respalda, y comprueba cada acción en código.
- Tiempo
- ~20 min
- Tema
- Prompting
- Stack
- python · anthropic · pydantic
Problema
Después de cada reunión, alguien tiene que repasar la grabación y escribir quién se comprometió a qué. Un resumen en prosa no sirve para eso: quieres una lista de acciones que puedas meter en tu gestor de tareas, cada una con su responsable y su fecha.
El riesgo de pedírselo a un modelo es que rellene huecos: asignar una tarea a alguien que no la asumió o inventar un plazo que nadie dijo. La receta lo ataca por dos lados: reglas claras en el prompt y una cita literal por acción que tu código comprueba contra la transcripción (grounding).
Ingredientes
- Python 3.10 o superior.
pip install anthropic pydantic- La variable de entorno
ANTHROPIC_API_KEY. - Una transcripción con el nombre de quien habla en cada intervención (
reunion.txt) y la lista de asistentes.
Pasos
-
Define la estructura de salida. Cada acción lleva
responsableyfecha_limiteque pueden sernull. Hacerlos opcionales es lo que permite al modelo decir «nadie la asumió» en vez de inventar. -
Escribe reglas explícitas. Qué cuenta como acción y qué es solo una idea, cómo resolver fechas relativas («el jueves») con la fecha de la reunión, y que la cita sea literal.
-
Comprueba en código lo que el esquema no puede. Que el responsable esté entre los asistentes, que la cita aparezca en la transcripción y que la fecha no sea anterior a la reunión. Lo que falle se marca para revisión.
import re
import sys
from datetime import date
import anthropic
from pydantic import BaseModel, Field
MODEL = "claude-sonnet-5-5"
client = anthropic.Anthropic()
class Accion(BaseModel):
tarea: str = Field(description="Qué hay que hacer, empezando por un verbo")
responsable: str | None = Field(description="Nombre exacto de un asistente, o null si nadie la asumió")
fecha_limite: date | None = Field(description="Solo si se dijo una fecha; si no, null")
cita: str = Field(description="Frase literal de la transcripción donde se acordó")
class Resumen(BaseModel):
resumen: str = Field(description="Tres o cuatro frases")
decisiones: list[str]
acciones: list[Accion]
temas_abiertos: list[str]
SISTEMA = """Resumes reuniones de trabajo a partir de su transcripción.
Reglas:
- Una acción es algo que alguien se comprometió a hacer. Las ideas sin dueño van a temas_abiertos.
- responsable: el nombre tal como aparece en la lista de asistentes. Si nadie la asumió, null.
- fecha_limite: solo si se dijo explícitamente. Convierte expresiones como "el viernes" usando la
fecha de la reunión. Si no hay fecha, null. No inventes plazos.
- cita: copia literalmente la frase de la transcripción que respalda la acción."""
def _normalizar(t: str) -> str:
return re.sub(r"\s+", " ", t).strip().lower()
def resumir(transcripcion: str, asistentes: list[str], fecha: date) -> tuple[Resumen, list[str]]:
resp = client.messages.parse(
model=MODEL,
max_tokens=4096,
system=SISTEMA,
messages=[{
"role": "user",
"content": f"Fecha de la reunión: {fecha.isoformat()}\n"
f"Asistentes: {', '.join(asistentes)}\n\n"
f"<transcripcion>\n{transcripcion}\n</transcripcion>",
}],
output_format=Resumen,
)
if resp.stop_reason != "end_turn":
raise RuntimeError(f"Respuesta incompleta: {resp.stop_reason}")
resumen = resp.parsed_output
# comprobaciones que el esquema no puede hacer
avisos = []
texto = _normalizar(transcripcion)
for a in resumen.acciones:
if a.responsable is not None and a.responsable not in asistentes:
avisos.append(f"'{a.tarea}': responsable '{a.responsable}' no está entre los asistentes")
if _normalizar(a.cita) not in texto:
avisos.append(f"'{a.tarea}': la cita no aparece en la transcripción")
if a.fecha_limite and a.fecha_limite < fecha:
avisos.append(f"'{a.tarea}': fecha límite anterior a la reunión")
return resumen, avisos
if __name__ == "__main__":
transcripcion = open(sys.argv[1], encoding="utf-8").read()
resumen, avisos = resumir(transcripcion, ["Marta", "Luis", "Irene"], date(2026, 10, 19))
print(resumen.model_dump_json(indent=2))
for a in avisos:
print("REVISAR:", a, file=sys.stderr)- Prueba con una transcripción corta:
Marta: Repasamos el lanzamiento. La landing sigue sin textos definitivos.
Luis: Yo me encargo de los textos, los tengo el jueves.
Irene: Habría que revisar los precios con finanzas en algún momento.
Marta: Vale. Decidimos lanzar el 3 de noviembre si los textos llegan a tiempo.
Irene: Yo preparo la campaña de correo para esa semana.Ejecuta python reunion.py reunion.txt.
Cómo comprobar que funciona
# salida de ejemplo
{
"resumen": "Se revisó el estado del lanzamiento...",
"decisiones": ["Lanzar el 3 de noviembre si los textos llegan a tiempo"],
"acciones": [
{"tarea": "Escribir los textos de la landing", "responsable": "Luis",
"fecha_limite": "2026-10-22", "cita": "Yo me encargo de los textos, los tengo el jueves."},
{"tarea": "Preparar la campaña de correo", "responsable": "Irene",
"fecha_limite": null, "cita": "Yo preparo la campaña de correo para esa semana."}
],
"temas_abiertos": ["Revisar los precios con finanzas"]
}- «El jueves» se resuelve a
2026-10-22, porque la reunión fue el lunes 19. - «Revisar los precios» va a
temas_abiertos: nadie lo asumió. - La campaña de Irene no tiene fecha concreta, así que
fecha_limiteesnull. - Si aparece alguna línea
REVISAR:en stderr, mira esa acción a mano antes de crear la tarea. - Para medir: resume 10 reuniones, anota a mano las acciones correctas y cuenta las que faltan y las inventadas (Evals como disciplina).
Variaciones
- Responsable como
enum. Construye el modelo en tiempo de ejecución con los asistentes de cada reunión:create_model("Accion", responsable=(Literal[tuple(asistentes)] | None, ...), ...). Así el esquema impide nombres fuera de la lista. - Reuniones largas. Para transcripciones de horas, pon la transcripción al principio y las instrucciones al final, o trocea por bloques de tiempo y fusiona las acciones (Trabajar con documentos largos).
- Enviar las tareas. Crea las tareas en tu gestor con una herramienta, y pide confirmación antes de asignarlas a otras personas (Aprobación humana antes de actuar).
- Sin nombres en la transcripción. Si solo tienes «Hablante 1, Hablante 2», pide que el responsable sea ese identificador y mapea a personas después.
Errores típicos
- Campos obligatorios para todo. Si
responsableno admitenull, el modelo pondrá un nombre aunque nadie se comprometiera. - Fechas relativas sin fecha de referencia. Sin decirle cuándo fue la reunión, «el viernes» no se puede resolver bien.
- Confundir intención con compromiso. «Habría que…» no es una acción con dueño. Dilo en las reglas y pon un ejemplo.
- No verificar la cita. Si no compruebas que la cita existe, pierdes la única forma barata de detectar acciones inventadas.
- Crear tareas sin revisión cuando hay avisos. Una tarea asignada a la persona equivocada genera más trabajo que no tenerla.