Skip to content

Instantly share code, notes, and snippets.

@Klerith
Created August 20, 2026 16:26
Show Gist options
  • Select an option

  • Save Klerith/93bd3a2bafe5a993d52380fa69f76c99 to your computer and use it in GitHub Desktop.

Select an option

Save Klerith/93bd3a2bafe5a993d52380fa69f76c99 to your computer and use it in GitHub Desktop.
PATRÓN: Encadenamiento de prompts
/**
* PATRÓN: Encadenamiento de prompts
* --------------------------------------------------
* Dos eslabones y un bucle. Nada más.
*
* Eslabón 1 (modelo) → escribe el anuncio borrador
* Gate (código) → ¿cabe en 280 caracteres?
* Eslabón 2 (modelo) → recorta, sabiendo cuánto le sobra
*
*
* Por qué este ejemplo: el modelo NO SABE contar caracteres.
* No es un problema de prompt. Es que ve tokens, no letras. Puedes
* escribirle "MÁXIMO 280 CARACTERES" en mayúsculas
* y puede devolver 340 con total confianza como 100 caracteres más.
*
* Y `text.length` nunca se equivoca.
*
* Esa asimetría es el patrón entero: el modelo es incapaz de verificar
* algo que una línea de código verifica siempre. La cadena no MEJORA el
* resultado, lo GARANTIZA.
*
* A) Sin cadena → una llamada y a rezar (esperar lo mejor)
* B) Con cadena → gate + reintentos + fallback determinista
* C) Riesgo → el mismo bucle, pero sin tope de reintentos (peligroso, puede no terminar)
*/
import { generateText } from 'ai';
import { createTracer, model } from '../../helpers/index.js';
// ---------------------------------------------------------------------------
// EL ENCARGO
// ---------------------------------------------------------------------------
const MAX_CHARS = 280;
const MAX_RETRIES = 3;
const COURSE = {
title: 'Patrones de diseño agéntico: Respuestas efectivas a desafíos',
hours: 18,
priceUSD: 34.99,
url: 'https://cursos.devtalles.com/cursos/patrones-diseno-agentico',
};
const BRIEF =
`Escribe el anuncio de lanzamiento de un curso para publicarlo en X.\n\n` +
`CURSO: ${COURSE.title}\n` +
`DURACIÓN: ${COURSE.hours} horas\n` +
`PRECIO: ${COURSE.priceUSD} USD\n` +
`ENLACE: ${COURSE.url}\n\n` +
`REQUISITOS:\n` +
` - Máximo ${MAX_CHARS} caracteres, contando espacios y el enlace.\n` +
// ` - Trata de usar todo el espacio disponible.\n` +
` - Usar al menos ${MAX_CHARS - 40} caracteres.\n` +
` - Incluye el título exacto, el precio y el enlace.\n` +
` - Tono directo, para desarrolladores. Sin markdown.\n` +
` - No emojis.\n` +
` - No sobre vendas, se honesto con lo que ofreces.\n` +
` - Devuelve ÚNICAMENTE el anuncio, sin comillas ni explicaciones.`;
// ---------------------------------------------------------------------------
// EL GATE — Líneas que el modelo no puede replicar
// ---------------------------------------------------------------------------
/**
* Fíjate en lo que devuelve: no un "hazlo más corto", "hazlo más largo",
* sino el excedente o el faltante exacto.
* EXACTO. Un buen gate no dice solo que no. Dice cuánto.
*/
const MIN_CHARS = MAX_CHARS - 40;
function lengthGate(text: string): string | undefined {
if (text.length > MAX_CHARS) {
return (
`El anuncio tiene ${text.length} caracteres. Te sobran ` +
`${text.length - MAX_CHARS}. Recórtalo a ${MAX_CHARS} o menos SIN ` +
`eliminar el título exacto, el precio ni el enlace.`
);
}
if (text.length < MIN_CHARS) {
return (
`El anuncio tiene ${text.length} caracteres y se queda corto: ` +
`te faltan ${MIN_CHARS - text.length} para el mínimo de ${MIN_CHARS}. ` +
`Amplíalo sin pasar de ${MAX_CHARS}: añade un beneficio concreto ` +
`del curso o menciona las ${COURSE.hours} horas.`
);
}
return undefined;
}
// ---------------------------------------------------------------------------
// EL JUEZ PROGRAMÁTICO
// ---------------------------------------------------------------------------
type Check = { label: string; passed: boolean };
function auditAnnouncement(text: string): Check[] {
return [
{
label: `≤ ${MAX_CHARS} caracteres (${text.length})`,
passed: text.length <= MAX_CHARS,
},
{
label: `Mínimo ${MAX_CHARS - 40} caracteres. Actual: ${text.length}. (falta: ${MAX_CHARS - 40 - text.length})`,
passed: text.length >= MAX_CHARS - 40,
},
{ label: 'Título exacto', passed: text.includes(COURSE.title) },
{ label: 'Precio', passed: /34[.,]99/.test(text) },
{ label: 'Enlace', passed: text.includes(COURSE.url) },
{ label: 'Sin markdown', passed: !/[#*_`]/.test(text) },
{
label: 'Sin emojis',
passed:
!/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/u.test(
text,
),
},
];
}
function printAudit(checks: Check[]) {
const passed = checks.filter((check) => check.passed).length;
for (const check of checks) {
const mark = check.passed ? '✓'.green : '✗'.red;
console.log(` ${mark} ${check.label}`);
}
console.log(` → ${passed}/${checks.length} requisitos cumplidos`.blue);
return passed;
}
// ---------------------------------------------------------------------------
// LOS ESLABONES
// ---------------------------------------------------------------------------
const WRITER_INSTRUCTIONS =
'Escribes anuncios cortos para redes sociales. Responde en español. ' +
'Devuelves solo el texto del anuncio, sin comillas, sin markdown, ' +
'sin explicar lo que hiciste.';
/** Eslabón 1 — el borrador. */
async function writeAnnouncement(tracer: ReturnType<typeof createTracer>) {
// TODO: implementar la función
return '';
}
/** Eslabón 2 — el recorte. Recibe el veredicto del gate, no algo vago. */
// Eventualmente lo puliremos para que sea más robusto y eficiente.
async function updateAnnouncement(
text: string, // anuncio actual
feedback: string, // feedback del gate
tracer: ReturnType<typeof createTracer>,
) {
// TODO: implementar la función
return '';
}
// ---------------------------------------------------------------------------
// A) SIN CADENA — una llamada y a rezar
// ---------------------------------------------------------------------------
async function withoutChaining() {
console.log('\n═══ A) SIN CADENA ═══\n'.blue);
const tracer = createTracer('sin-cadena');
// TODO: implementar la lógica
console.log(
`\n ⚠️ El requisito estaba en el prompt.
El modelo no puede cumplirlo de forma fiable porque no cuenta caracteres
procesa tokens. Aquí no hay prompt que valga.`.yellow,
);
return { ...tracer.summary(), retries: 0 }; // score
}
// ---------------------------------------------------------------------------
// B) CON CADENA — gate, reintentos acotados y fallback determinista
// ---------------------------------------------------------------------------
async function withChaining() {
console.log('\n═══ B) CON CADENA ═══\n'.blue);
const tracer = createTracer('con-cadena');
// TODO: implementar la lógica
return { ...tracer.summary() }; // score, retries
}
// ---------------------------------------------------------------------------
// C) RIESGO — el mismo bucle, sin tope de reintentos
// ---------------------------------------------------------------------------
/** Tope de seguridad para el laboratorio. En el bucle "sin tope" real, no lo habría. */
const SAFETY_LIMIT = 8;
async function chainWithoutRetryLimit() {
console.log('\n═══ C) RIESGO: BUCLE SIN TOPE ═══\n'.blue);
const tracer = createTracer('sin-tope');
// TODO: implementar la lógica
console.log(
`\n\n
⚠️ Mira la trayectoria. El modelo se acerca a la solución y se queda
rondando: 4 → 3 → 2 → 1 → 1 → 1 → 2 → 1...
Cada iteración cuesta tokens.
Sin tope, esto puede no terminar.
Todo bucle necesita un techo Y una salida determinista.`.yellow,
);
return { ...tracer.summary() }; // score, retries, retries
}
// ---------------------------------------------------------------------------
// EJECUCIÓN DEL PROGRAMA
// ---------------------------------------------------------------------------
export async function promptChainingMain() {
const a = await withoutChaining();
// const b = await withChaining();
// const c = await chainWithoutRetryLimit();
console.log('\n═══ COMPARATIVA ═══\n'.blue);
console.table({
'Sin cadena': a,
// 'Con cadena': b,
// 'Bucle sin tope': c,
});
console.log(
'\n El gate son unas pocas líneas de TypeScript y decide algo que el\n' +
' modelo no puede decidir. Esa es toda la idea del patrón.\n\n' +
' La cadena no mejora el anuncio: garantiza que cabe y es correcto.\n' +
' "Mejor a veces" y "correcto siempre" no son lo mismo.\n\n' +
' Y el eslabón que cierra el caso no es un prompt: es el slice.\n',
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment