Guía del SDK de Patter para crear un agente telefónico de reservas de restaurantes con variables dinámicas, barreras de seguridad, paneles de latencia y comprobaciones de evaluación

En este tutorial, exploramos el SDK de Patter mediante la creación de un flujo de trabajo de agente de voz que simula cómo se comporta un asistente telefónico con IA durante conversaciones reales. Trabajamos con un caso de uso de reserva de restaurante en el que definimos variables dinámicas de la persona que llama, registramos herramientas a las que se puede llamar, aplicamos barreras de seguridad de salida, simulamos el comportamiento de voz a texto y de texto a voz y ejecutamos un flujo de llamadas completo con guión sin requerir credenciales de telefonía en vivo. También inspeccionamos la API de Patter instalada cuando está disponible, creamos un cerebro de agente determinista, realizamos un seguimiento de la latencia modelada y las métricas de costos, y validamos el sistema mediante evaluaciones de estilo de regresión. Finalmente, entendemos cómo el SDK de Patter integra la lógica del agente, el uso de herramientas, los controles de seguridad, la simulación de llamadas y los patrones de implementación del mundo real en una única canalización estructurada de agentes de voz.

de __future__ importar anotaciones importar sys, subproceso, importlib, inspeccionar, tiempo, json, re, aleatorio, ajuste de texto, sistema operativo de clases de datos importar clase de datos, campo de estadísticas importar mediana def _try_install(pkg: str) -> Ninguno: intente: subprocess.run([sys.executable, "-m", "pip", "install", "-q", pkg], check=False, timeout=600) excepto Excepción: pase def _load_patter(): """Devuelve el módulo getpatter real si se puede importar; de lo contrario, Ninguno.""" para el nombre en ("patter", "getpatter"): intente: devuelva importlib.import_module(nombre) excepto Excepción: continúe, devuelva Ninguno _PATTER = _load_patter() si _PATTER es Ninguno: _try_install("getpatter") _PATTER = _load_patter() def show_real_api(): """Imprime la API de Patter *actual* instalada para que el tutorial se adapte a cualquier versión que haya extraído Colab (getpatter es joven y se mueve semanalmente).""" print("=" * 74) print("PATTER SDK — API instalada") print("=" * 74) si _PATTER es Ninguno: print("getpatter no se puede importar en este kernel (está bien, lan" "La demostración de Colab a continuación es independiente). En un Colab nuevo,n" "`pip install getpatter` y este bloque imprime la API activa.n") return print(f"module: {_PATTER.__name__}") print(f"version: {getattr(_PATTER, '__version__', 'unknown')}") exported = [n for n in dir(_PATTER) if not n.startswith('_')] print("exports :", ", ".join(exportado[:24]) + (" …" if len(exportado) > 24 else "")) Patter = getattr(_PATTER, "Patter", Ninguno) si Patter no es Ninguno: para metanfetamina en ("__init__", "agent", "serve", "call", "test", "tool"): fn = getattr(Patter, meth, Ninguno) si es invocable(fn): intente: print(f"Patter.{meth:<8}: {inspect.signature(fn)}") excepto (TypeError, ValueError): print(f"Patter.{meth:<8}: ") print() random.seed(7) CALL_VARIABLES = { "customer_name": "Priya", "loyalty_tier": "Gold", "restaurant": "Acme Bistro", } USE_REAL_LLM = False HERRAMIENTAS: dict[str, dict] = {} def herramienta(descripción: str): def deco(fn): params = [p for p in inspect.signature(fn).parameters] HERRAMIENTAS[fn.__name__] = {"fn": fn, "description": descripción, "params": params} return fn return deco import copy _OPEN_TABLES_INIT = { ("today", "tarde"): 6, ("hoy", "tarde"): 2, ("mañana", "almuerzo"): 8, ("mañana", "tarde"): 4, ("viernes", "tarde"): 0, ("viernes", "tarde"): 3, } _RES_DB_INIT = {"AC8842": "Mesa para 2, mañana a las 7:30 pm, bajo Singh — confirmado."} _HOURS = {"weekday": "11:00–22:00", "weekend": "10:00–23:00"} _OPEN_TABLES = copy.deepcopy(_OPEN_TABLES_INIT) _RES_DB = copy.deepcopy(_RES_DB_INIT) def _reset_backend(): """Cada llamada simulada comienza desde un backend limpio (una llamada real llega a una nueva conexión de base de datos). Mantiene el conjunto de evaluación determinista entre ejecuciones.""" _OPEN_TABLES.clear(); _OPEN_TABLES.update(copy.deepcopy(_OPEN_TABLES_INIT)) _RES_DB.clear(); _RES_DB.update(copy.deepcopy(_RES_DB_INIT)) @tool("Compruebe si las mesas están libres para una fecha/hora y el tamaño del grupo.") def check_availability(date: str, slot: str, party_size: int) -> str: asientos = _OPEN_TABLES.get((fecha, espacio), 0) if asientos >= party_size: return f"DISPONIBLE: {asientos} asientos abiertos para {fecha} {ranura}." return f"FULL: solo {asientos} asientos para {fecha} {espacio} (necesita {party_size})." @tool("Reserve una mesa y devuelva un código de confirmación.") def book_table(nombre: str, fecha: str, slot: str, party_size: int) -> str: asientos = _OPEN_TABLES.get((fecha, slot), 0) if asientos < party_size: return f"FAILED: no hay suficientes asientos para {party_size} el {fecha} {slot}." _OPEN_TABLES[(fecha, espacio)] = asientos – código party_size = "AC" + str(random.randint(1000, 9999)) _RES_DB[código] = f"Mesa para {party_size}, {fecha} {espacio}, bajo {nombre}." return f"RESERVADO: código {código} — grupo {tamaño_grupo}, {fecha} {espacio}, {nombre}." @tool("Devolver el horario de apertura para un tipo de día determinado.") def get_hours(day_type: str) -> str: return _HOURS.get(day_type, _HOURS["weekday"]) @tool("Buscar una reserva existente por su código de confirmación.") def lookup_reservation(code: str) -> str: return _RES_DB.get(code.upper(), "NOT_FOUND: no hay reserva con ese código.") @tool("Entregar la llamada a un host humano (Patter autoinyecta transfer_call).") def transfer_to_human(razón: str) -> str: return f"TRANSFERENCIA: enrutamiento a un host – motivo: {razón}."

Configuramos el entorno del tutorial importando las bibliotecas necesarias, instalando opcionalmente el SDK de Patter e inspeccionando la API instalada cuando esté disponible. Definimos las variables dinámicas de las personas que llaman, creamos un pequeño registro de herramientas y preparamos un backend de restaurante en memoria para disponibilidad, reservas, horarios y traslados. También registramos las herramientas principales que permiten a nuestro agente telefónico simulado verificar mesas, hacer reservas, buscar códigos de confirmación y enrutar a las personas que llaman a un humano.

Agregar barreras de seguridad de salida y capas de voz simuladas

clase GuardrailBlock(Excepción): def __init__(self, safe_reply: str): self.safe_reply = respuesta_segura _PII_EMAIL = re.compile(r"b[w.+-]+@[w-]+.[w.-]+b") _PII_PHONE = re.compile(r"b(?:+?d[s-]?){9,13}db") _INTERNAL = re.compile(r"bCUST-d{4,}b") _BANNED = re.compile(r"b(maldita sea|infierno|basura)b", re.I) _OFFTOPIC = re.compile(r"b(diagnos|prescri|demanda|asesoramiento legal|medicación)b", re.I) def gr_redact_pii(text: str, ctx: dict) -> str: text = _PII_EMAIL.sub("[correo electrónico oculto]", texto) text = _PII_PHONE.sub("[número oculto]", texto) return text def gr_hide_internal_ids(texto: str, ctx: dict) -> str: return _INTERNAL.sub("su cuenta", texto) def gr_profanity(text: str, ctx: dict) -> str: return _BANNED.sub("—", text) def gr_scope(text: str, ctx: dict) -> str: if _OFFTOPIC.search(text): rise GuardrailBlock("Solo soy la línea de reserva, así que no puedo ayudar con " "eso — pero puedo hacer una reserva si lo desea.") return text def gr_concise(text: str, ctx: dict) -> str: parts = re.split(r"(?<=[.!?])s+", text.strip()) return " ".join(parts[:2]) if len(parts) > 2 else text GUARDRAILS = [gr_scope, gr_hide_internal_ids, gr_redact_pii, gr_profanity, gr_concise] def apply_guardrails(text: str, ctx: dict) -> str: for g in GUARDRAILS: text = g(text, ctx) return text _WHISPER_FILLERS = {"tú", "gracias", ".", "uh", "um"} def fake_stt(enunciado: str) -> tupla[str, float]: """Return (transcript, latency_ms). Suelta rellenos estilo Whisper como lo hace el pipeline de Patter.""" t0 = time.perf_counter() tokens = [w for w in utterance.split() if w.lower().strip(".,") not in _WHISPER_FILLERS] transcript = " ".join(tokens) if tokens else utterance lat = 60 + len(utterance) * 1.5 + random.uniform(0, 25) _spin(t0) devuelve transcripción, lat def fake_tts(text: str) -> float: """Devuelve síntesis latency_ms (time-to-first-audio-ish).""" return 90 + len(text) * 0.8 + random.uniform(0, 30) def _spin(_t0): pasa SYSTEM_PROMPT = ( "Tú es el amable anfitrión telefónico de {restaurant}. Persona que llama: {customer_name} " "(miembro de {loyalty_tier}). Ayúdelos a reservar, consultar horarios, buscar una " "reserva o comunicarse con un humano". ) FIRST_MESSAGE = "Hola, {customer_name}, gracias por llamar a {restaurante}. def _fill(t: str, v: dict) -> str: for k, val in v.items(): t = t.replace("{" + k + "}", str(val)) return t def parse_party(s: str): m = re.search(r"(?:for|party of|table for|of)s+(d+)", s) o re.search(r"b(d+)s*(?:personas|invitados|de nosotros|pax)", s) if m: return int(m.group(1)) para w, n en _NUM.items(): if re.search(rf"b{w}b(?:s+(?:personas|invitados|de nosotros))?", s): return n return Ninguno def parse_date(s: str): for d in ("hoy", "esta noche", "mañana", "viernes"): if d in s: return "hoy" if d == "esta noche" else d return Ninguno def parse_slot(s: str): if re.search(r"b(almuerzo|mediodía|mediodía)b", s): devuelve "almuerzo" if re.search(r"b(late|11pm|11 pm|después de las 10)b", s): return "tarde" si re.search(r"b(cena|tarde|esta noche|7|8|9|pm)b", s): return "tarde" return Ninguno def parse_name(s: str): m = re.search(r"(?:i'?m|this is|name is|under)s+([AZ][az]+)", s) return m.group(1) if m else Ninguno def parse_code(s: str): m = re.search(r"b(ACd{3,4})b", s.upper()) return m.group(1) if m else Ninguno def may_real_llm(history, user_text, ctx): """Opcional: diferir la charla trivial de forma libre a un LLM real si USE_REAL_LLM + una clave. Devuelve una cadena o Ninguna. Se mantiene pequeña y completamente opcional.""" si no USE_REAL_LLM: return Ninguno intente: if os.environ.get("OPENAI_API_KEY"): from openai import OpenAI sys_p = _fill(SYSTEM_PROMPT, ctx["vars"]) msgs = [{"role": "system", "content": sys_p}] + historial + [{"role": "user", "content": user_text}] r = OpenAI().chat.completions.create(model="gpt-4o-mini", mensajes=msgs, max_tokens=60) devuelve r.choices[0].message.content excepto Excepción: devolver Ninguno devolver Ninguno

Construimos la capa de barrera de salida que mantiene al asistente telefónico seguro, conciso y apropiado para el caso de uso de la reserva. Redactamos información confidencial, ocultamos identificaciones internas de clientes, limpiamos lenguaje no deseado, bloqueamos solicitudes fuera de tema y mantenemos respuestas breves para una mejor experiencia telefónica. Luego simulamos el comportamiento de voz a texto y de texto a voz, definimos el mensaje del sistema y agregamos funciones de análisis ligeras para el tamaño del grupo, la fecha, el horario, el nombre y el código de reserva.

Construyendo el cerebro del agente y el simulador de llamadas

def agent_brain(history: list, user_text: str, ctx: dict): """Lógica central. `ctx` lleva vars + estado de ranura entre turnos.""" s = user_text.lower() st = ctx["state"] if re.search(r"b(human|agent|representative|manager|person)b", s): return ("__tool__", "transfer_to_human", {"reason": "la persona que llama solicitó un humano"}) if st.get("reservado") y re.search(r"b(no|no|eso es todo|eso es todo|adiós|gracias|gracias)b", s): return "Gracias por llamar, ¡hasta pronto!" if re.search(r"b(weather|stock|broma|medicamento|demanda)b", s): return "Solo soy la fila de reservas para las mesas de esta noche. ¿Quieres que te traiga una?" code = parse_code(s) if code o "buscar" en s o "mi reserva" en s: if code: return ("__tool__", "lookup_reservation", {"code": code}) st["intent"] = "lookup" return "Claro, ¿cuál es su código de confirmación? Parece AC seguido de cuatro dígitos". if re.search(r"b(horas|abierto|cerrado|cerrando)b", s): tipo_día = "fin de semana" if re.search(r"b(sáb|domingo|fin de semana)b", s) else "día laborable" return ("__tool__", "get_hours", {"day_type": tipo_día}) if re.search(r"b(libro|reserva|mesa|reserva)b", s) o st.get("intent") == "book": st["intent"] = "book" if st.get("intent") == "book": para clave, val in (("party_size", parse_party(s)), ("date", parse_date(s)), ("slot", parse_slot(s)), ("name", parse_name(user_text) o st.get("name"))): si val no es Ninguno: st[key] = val si st.get("party_size") es Ninguno: devuelve "Encantado de reservarte, ¿cuántas personas?" si st.get("date") es Ninguno: return f"Genial, una mesa para {st['party_size']}. ¿Qué día: hoy, mañana o viernes?" si st.get("slot") es Ninguno: return "¿Y el almuerzo, la cena o el descanso hasta tarde?" si no, st.get("checked"): st["checked"] = True return ("__tool__", "check_availability", {"date": st["date"], "slot": st["slot"], "party_size": st["party_size"]}) si st.get("name") es Ninguno: devuelve "¿Con qué nombre debería ponerlo?" if not st.get("booked"): st["booked"] = True return ("__tool__", "book_table", {"name": st["name"], "date": st["date"], "slot": st["slot"], "party_size": st["party_size"]}) return "¿Estás listo, algo más?" if re.search(r"b(no|no|eso es todo|adiós|gracias)b", s): return "Gracias por llamar. ¡Hasta pronto!" return may_real_llm(history, user_text, ctx) o "Puedo reservar una mesa, consultar horarios o buscar una reserva, ¿qué le gustaría?" def fold_tool_result(tool_name: str, raw: str, ctx: dict) -> str: """Convertir el resultado de una herramienta sin procesar en una respuesta hablada natural (lo que hace el LLM con el resultado de una llamada de función en una llamada de Patter real).""" if tool_name == "check_availability": if raw.startswith("AVAILABLE"): return "Buenas noticias: ese espacio está abierto. ¿Con qué nombre debo ponerlo?" ctx["state"]["checked"] = False ctx["state"]["slot"] = Ninguno return "Ese está lleno, lo siento. ¿Funcionaría en otro horario: almorzar o sentarse hasta tarde?" si nombre_herramienta == "book_table" y raw.startswith("RESERVADO"): código = raw.split("código")[1].dividir(" ")[0]return f"¡Reservado! Su código de confirmación es {código}. ¿Algo más?" if tool_name == "get_hours": return f"Estamos abiertos {raw}. ¿Quieres que reserve una mesa?" if nombre_herramienta == "lookup_reservation": return ("Aquí está: " + raw) if not raw.startswith("NOT_FOUND") else "No pude encontrar ese código. ¿Podrías leerlo una vez más?" if tool_name == "transfer_to_human": return "Por supuesto, conectándote con un host ahora. ¡Un momento!" devolver raw @dataclass clase Turno: altavoz: str texto: str stt_ms: float = 0.0 llm_ms: float = 0.0 tool_ms: float = 0.0 tts_ms: float = 0.0 herramienta: str | Ninguno = Ninguno @property def total_ms(self): return self.stt_ms + self.llm_ms + self.tool_ms + self.tts_ms @dataclass clase CallResult: transcripción: lista = campo(default_factory=list) turnos: lista = campo(default_factory=list) def run_call(caller_lines: list[str], variables: dict, barge_in_at: int | Ninguno = Ninguno) -> CallResult: _reset_backend() ctx = {"vars": dict(variables), "state": {}} historial, res =[], CallResult() def hablar(texto: str, llm_ms: float, herramienta=None, tool_ms=0.0, truncate=None): t0 = time.perf_counter() try: seguro = apply_guardrails(texto, ctx) excepto GuardrailBlock como b: seguro = b.safe_reply si truncado: seguro = seguro.split()[:truncado] seguro = " ".join(seguro) + " —" gr_ms = (time.perf_counter() – t0) * 1000 tts = fake_tts(seguro) res.transcript.append(("agente", seguro)) res.turns.append(Turn("agente", seguro, llm_ms=llm_ms + gr_ms, herramienta=herramienta, herramienta_ms=tool_ms, tts_ms=tts)) History.append({"role": "asistente", "content": seguro}) hablar(_fill(FIRST_MESSAGE, ctx["vars"]), llm_ms=5.0) para i, raw_line en enumerate(caller_lines): transcripción, stt_ms = fake_stt(raw_line) res.transcript.append(("llamante", transcripción)) History.append({"role": "user", "content": transcript}) t0 = time.perf_counter() out = agent_brain(history, transcript, ctx) llm_ms = (time.perf_counter() – t0) * 1000 + random.uniform(40, 120) truncate = 4 if (barge_in_at no es Ninguno y i == barge_in_at) else Ninguno si isinstance(fuera, tupla) y fuera y fuera[0]== "__tool__": _, nombre, kwargs = out t1 = time.perf_counter() raw = HERRAMIENTAS[nombre]["fn"](**kwargs) tool_ms = (time.perf_counter() – t1) * 1000 + random.uniform(10, 40) respuesta = fold_tool_result(nombre, raw, ctx) hablar(respuesta, llm_ms=llm_ms, herramienta=nombre, herramienta_ms=tool_ms, truncate=truncar) res.turns[-1].stt_ms = stt_ms else: hablar(out, llm_ms=llm_ms, truncate=truncar) res.turns[-1].stt_ms = stt_ms return res

Implementamos el cerebro del agente principal que controla el flujo de la conversación a través de reservas, búsqueda de reservas, preguntas sobre horarios de apertura, transferencias humanas y respuestas alternativas. Usamos la entrada analizada de la persona que llama y el estado de la conversación almacenada para decidir cuándo hacer preguntas de seguimiento, llamar a herramientas o completar una reserva. También creamos un simulador de llamadas con objetos de turno estructurados para ejecutar conversaciones programadas y recopilar datos de latencia, herramientas y transcripciones.

Impresión de transcripciones, paneles de latencia y evaluaciones de regresión

def print_transcript(title: str, res: CallResult): print("n" + "─" * 74) print(f"📞 {title}") print("─" * 74) para quién, txt en res.transcript: tag = "🤖 agente " if who == "agente" else "🧑 llamante" print(f"{tag}│ {txt}") def print_dashboard(res: CallResult): totales = [t.total_ms para t en res.turns] stt = [t.stt_ms para t en res.turns si t.stt_ms] llm = [t.llm_ms para t en res.turns] tts = [t.tts_ms para t en res.turns] tool_turns = [t para t en res.turns si t.tool] def p95(xs): devuelve ordenado(xs)[max(0, int(len(xs) * 0.95) – 1)] si xs else 0 costo = suma(0.0009 para _ en stt) + suma(0.0004 para _ en res.turns) + suma(0.00018 * len(t.text) para t en res.turns) print("n ┌─ Patrón tablero (modelado) ────────────────────────────┐") print(f" │ turnos del agente: {len(res.turns):<6} │") print(f" │ llamadas a herramientas: {len(tool_turns):<6} ({', '.join(t.tool for t in tool_turns) or '—'})") print(f" │ latencia p50 total: {mediana(totales):6.0f} ms │") print(f" │ latencia p95 total: {p95(totales):6.0f} ms │") print(f" │ Promedio STT / Promedio TTS : {(suma(stt)/len(stt) si stt else 0):5.0f} ms / {(suma(tts)/len(tts) si tts else 0):5.0f} ms │") print(f" │ gasto est. (ilus.): ${costo:5.3f} │") print(" └────────────────────────────── ────────────────────────────┘") def run_evals() -> bool: print("n" + "=" * 74) print("EVAL HARNESS — comprobaciones de regresión") print("=" * 74) casos, aprobados =[], 0 def full_transcript(res): return " || ".join(f"{w}:{t}" for w, t in res.transcript) r1 = run_call(["Me gustaría reservar una mesa", "nosotros cuatro", "mañana", "cena", "bajo Priya"], CALL_VARIABLES) ok1 = bool(re.search(r"el código de confirmación es ACd{4}", full_transcript(r1))) cases.append(("reserva una mesa y devuelve un código", ok1)) fuga = apply_guardrails("Su registro CUST-99812 muestra estado VIP.", {"vars": {}, "state": {}}) ok2 = "CUST-99812" no está en la filtración y "su cuenta" en la filtración cases.append(("guardrail oculta los CUST-ids internos", ok2)) r3 = run_call(["¿puedes darme consejos sobre medicamentos?"], CALL_VARIABLES) ok3 = "línea de reserva" en full_transcript(r3).lower() cases.append(("se niega a estar fuera del alcance (médico)", ok3)) r4 = run_call(["consígueme un humano por favor"], CALL_VARIABLES) ok4 = any(t.tool == "transfer_to_human" para t en r4.turns) cases.append(("transferencias a un humano a pedido", ok4)) r5 = run_call(["reservar una mesa", "dos", "viernes", "noche"], CALL_VARIABLES) ok5 = "completo" en full_transcript(r5).lower() y "código de confirmación" no en full_transcript(r5).lower() cases.append(("maneja un espacio completo con gracia", ok5)) long = apply_guardrails("Uno. Dos. Tres. Cuatro.", {"vars": {}, "state": {}}) ok6 = long.count(".") <= 2 cases.append(("conciso conteo de oraciones con límites de barandilla", ok6)) para nombre, ok en casos: aprobado += ok print(f" [{'PASS' if ok else 'FALLO'}] {nombre}") print(f"n {aprobado}/{len(casos)} aprobado") return aprobado == len(casos)

Formateamos los resultados de las llamadas simuladas en una transcripción legible y un panel estilo Patter que resume los turnos de los agentes, las llamadas de herramientas, la latencia y el gasto estimado. También creamos un sistema de evaluación determinista que verifica si el agente completa las reservas, protege las identificaciones internas, rechaza solicitudes médicas fuera de alcance, transfiere a un humano, maneja espacios completos y mantiene respuestas concisas. Usamos estas comprobaciones para validar que el flujo de trabajo del agente telefónico se comporta de manera confiable antes de avanzar hacia una implementación real.

Pasar a llamadas reales con Twilio y OpenAI Realtime

REAL_DEPLOYMENT = textwrap.dedent(''' # —- real_agent.py (ejecute FUERA de Colab; necesita un operador pago + claves) —- # export TWILIO_ACCOUNT_SID=AC… ; export TWILIO_AUTH_TOKEN=… # export TWILIO_PHONE_NUMBER=+1… ; export OPENAI_API_KEY=sk-… import asyncio from getpatter import Patter, Twilio, OpenAIRealtime phone = Patter(carrier=Twilio(), phone_number="+15550001234") # registre las mismas herramientas que probó anteriormente @phone.tool async def book_table(name: str, date: str, slot: str, party_size: int) -> str: … # su verdadero agente backend de reservas = phone.agent( motor=OpenAIRealtime(), # o canalización: stt=DeepgramSTT(), tts=ElevenLabsTTS() system_prompt="Usted es el anfitrión de Acme Bistro. Persona que llama: {nombre_cliente}.", first_message="Hola, {nombre_cliente}, ¡gracias por llamar a Acme Bistro!", variables={"nombre_cliente": "Priya"}, # herramientas dinámicas por persona que llama=[book_table], guardrails=["no_pii", "stay_in_scope"], # barreras de seguridad de salida) async def main(): # entrante: Tunnel=True genera un túnel de Cloudflare + apunta tu número await phone.serve(agent, Tunnel=True, Dashboard=True, Recording=True) # saliente en su lugar: # await phone.call(to="+15558675309", agente=agent, machine_detection=True) asyncio.run(main()) # O omitir el código por completo y probar desde un shell: patter dev real_agent.py ''').strip() def main(): show_real_api() demoA = run_call( caller_lines=[ "Hola, me gustaría reservar una mesa", "seremos cuatro", "mañana", "para cenar", "ponlo debajo de Priya", "no, eso es todo, gracias", ], variables=CALL_VARIABLES, ) print_transcript("Demo A – reserva (herramientas + variables dinámicas)", demoA) print_dashboard(demoA) demoB = run_call( caller_lines=[ "¿cuáles son tus horarios de fin de semana?", "en realidad, ¿puedo hablar con un humano", ], variables=CALL_VARIABLES, barge_in_at=1, ) print_transcript("Demostración B – horas, irrupción y transferencia humana", demoB) print_dashboard(demoB) all_green = run_evals() print("n" + "=" * 74) print("GRADUARSE A LLAMADAS REALES (copiar a un archivo local, no a Colab)") print("=" * 74) print(REAL_DEPLOYMENT) print("n✅ tutorial finalizado" + (" • evals green" if all_green else " • evals RED")) si __nombre__ == "__main__": principal()

Preparamos una plantilla de implementación de estilo producción que muestra cómo la misma lógica probada se puede trasladar de Colab a una configuración real de agente telefónico. Incluimos la estructura para usar Patter con Twilio, OpenAI Realtime, herramientas registradas, variables dinámicas, barreras de seguridad, servicio entrante, monitoreo de panel, grabación y llamadas salientes. Luego ejecutamos el tutorial completo mostrando la API instalada, ejecutando dos llamadas de demostración, imprimiendo paneles, ejecutando evaluaciones y mostrando el código de implementación final de la llamada real.

Conclusión

En conclusión, creamos un flujo de trabajo de agente telefónico completo al estilo Patter que refleja la estructura central de un sistema de inteligencia artificial de voz de producción. Creamos herramientas para verificar la disponibilidad, reservar mesas, buscar reservas y transferir personas que llaman a humanos, y luego las combinamos con barreras de seguridad, conversaciones escritas, latencia simulada, paneles y verificaciones de evaluación. También vimos cómo la misma lógica probada puede pasar posteriormente de una simulación de Colab autónoma a llamadas telefónicas reales a través de Twilio, OpenAI Realtime, túneles y la interfaz de implementación de Patter. Además, terminamos con una sólida comprensión de cómo podemos crear prototipos, probar, monitorear y preparar una aplicación de agente de voz antes de conectarla a la infraestructura de telefonía en vivo.

Consulte los códigos completos con Notebook aquí. Además, no dude en seguirnos en Twitter y no olvide unirse a nuestro SubReddit de más de 150.000 ml y suscribirse a nuestro boletín. ¡Esperar! estas en telegrama? Ahora también puedes unirte a nosotros en Telegram.

¿Necesita asociarse con nosotros para promocionar su repositorio de GitHub O su página principal de Hugging O su lanzamiento de producto O seminario web, etc.? Conéctate con nosotros

A Sana Hassan, pasante de consultoría en Marktechpost y estudiante de doble titulación en IIT Madras, le apasiona aplicar la tecnología y la inteligencia artificial para abordar los desafíos del mundo real. Con un gran interés en resolver problemas prácticos, aporta una nueva perspectiva a la intersección de la IA y las soluciones de la vida real.