create-panal-agent 0.20.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/src/salida.ts +101 -6
package/package.json
CHANGED
package/template/src/salida.ts
CHANGED
|
@@ -438,9 +438,22 @@ export function comoAdjunto(nombre: string): string {
|
|
|
438
438
|
const PIDE_UN_NOMBRE =
|
|
439
439
|
'You name files. Given a client request, reply with ONLY a file name for the deliverable.\n' +
|
|
440
440
|
'Two to five words. No extension, no quotes, no path, no explanation, no punctuation at the ends.\n' +
|
|
441
|
-
'Name the SUBJECT the work is about, never the action asked for
|
|
442
|
-
|
|
443
|
-
|
|
441
|
+
'Name the SUBJECT the work is about, never the action asked for.\n' +
|
|
442
|
+
// El ejemplo de antes enseñaba lo contrario de lo que pedía: a una petición
|
|
443
|
+
// en INGLÉS le contestaba en español («division de dos enteros»), y el
|
|
444
|
+
// encargo #95, escrito en inglés, salió como «contactos-en-json». Ahora hay
|
|
445
|
+
// un ejemplo por idioma, cada uno respondido en el suyo.
|
|
446
|
+
'Write it in the language of the client\'s INSTRUCTIONS — what they ask for — not the language of ' +
|
|
447
|
+
'the data they paste or of field names they spell out, and in their own script. No file-format ' +
|
|
448
|
+
'words (JSON, PDF, CSV). Examples: "write the test cases for a function that divides two integers" ' +
|
|
449
|
+
'-> "division of two integers"; "escribe los casos de prueba de una función que divide dos enteros" ' +
|
|
450
|
+
'-> "division de dos enteros".\n' +
|
|
451
|
+
// Decirlo no bastaba: con los datos en español y las instrucciones en
|
|
452
|
+
// inglés, el modelo seguía contestando en español. Obligarle a nombrar
|
|
453
|
+
// primero el idioma de las instrucciones es lo que lo corrige (medido).
|
|
454
|
+
'First decide the language of the instructions. Answer in exactly two lines:\n' +
|
|
455
|
+
'LANG: <ISO code of the instructions\' language>\n' +
|
|
456
|
+
'NAME: <the file name, in that language>';
|
|
444
457
|
|
|
445
458
|
/**
|
|
446
459
|
* El nombre del archivo que se entrega, sacado del TEMA del encargo.
|
|
@@ -461,7 +474,79 @@ const PIDE_UN_NOMBRE =
|
|
|
461
474
|
* devuelve algo que no sirve, se usa el nombre de siempre. Nombrar un archivo
|
|
462
475
|
* no puede impedir entregarlo: el pago ya está bloqueado.
|
|
463
476
|
*/
|
|
464
|
-
|
|
477
|
+
/**
|
|
478
|
+
* De «LANG: en\nNAME: contact list», la línea del nombre.
|
|
479
|
+
*
|
|
480
|
+
* Sin la etiqueta —un modelo que no siga el formato— se queda la respuesta
|
|
481
|
+
* entera, y `comoNombre` se queda con su primera línea, como antes.
|
|
482
|
+
*/
|
|
483
|
+
export function lineaDelNombre(crudo: string): string {
|
|
484
|
+
const m = crudo.match(/^\s*NAME\s*:\s*(.+)$/im);
|
|
485
|
+
return m ? m[1]! : crudo;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* EL IDIOMA DE LAS INSTRUCCIONES, sin los datos delante.
|
|
490
|
+
*
|
|
491
|
+
* Pedirle al modelo «escribe en el idioma del cliente» no funciona cuando el
|
|
492
|
+
* encargo trae datos en otro idioma: medido el 2026-09-28, con órdenes en
|
|
493
|
+
* inglés y una lista de contactos en español, las claves salían en español
|
|
494
|
+
* más de la mitad de las veces, y un detector que veía el encargo entero
|
|
495
|
+
* acertaba 19 de 30 (tomaba un encargo en francés por español, y uno en chino
|
|
496
|
+
* por inglés). El mismo detector con SOLO el primer párrafo acertó 39 de 40.
|
|
497
|
+
*
|
|
498
|
+
* El primer párrafo es lo que va antes de la primera línea en blanco, que es
|
|
499
|
+
* como se escribe casi siempre un encargo: primero qué hay que hacer, después
|
|
500
|
+
* los datos. Si es demasiado corto para decir nada, se usan los primeros 300
|
|
501
|
+
* caracteres.
|
|
502
|
+
*/
|
|
503
|
+
/**
|
|
504
|
+
* El nombre del idioma, en inglés, para decírselo al modelo.
|
|
505
|
+
*
|
|
506
|
+
* Con el código solo («"en"») el modelo seguía nombrando en español un encargo
|
|
507
|
+
* en inglés que citaba campos en español; con «Write the NAME in English» y el
|
|
508
|
+
* aviso de que ni los datos ni los campos citados deciden, acertó 12 de 12.
|
|
509
|
+
*/
|
|
510
|
+
const NOMBRES_DE_IDIOMA: Record<string, string> = {
|
|
511
|
+
en: 'English', es: 'Spanish', pt: 'Portuguese', fr: 'French', de: 'German', it: 'Italian',
|
|
512
|
+
nl: 'Dutch', ca: 'Catalan', zh: 'Chinese', ja: 'Japanese', ko: 'Korean', hi: 'Hindi',
|
|
513
|
+
bn: 'Bengali', ur: 'Urdu', ar: 'Arabic', fa: 'Persian', he: 'Hebrew', ru: 'Russian',
|
|
514
|
+
uk: 'Ukrainian', pl: 'Polish', tr: 'Turkish', vi: 'Vietnamese', id: 'Indonesian', th: 'Thai',
|
|
515
|
+
el: 'Greek', sv: 'Swedish',
|
|
516
|
+
};
|
|
517
|
+
export function nombreDeIdioma(codigo: string): string {
|
|
518
|
+
return NOMBRES_DE_IDIOMA[codigo] ?? `the language with ISO code "${codigo}"`;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const PIDE_EL_IDIOMA =
|
|
522
|
+
'You detect the language of a request. Reply with ONLY the ISO 639-1 code (two letters) of the ' +
|
|
523
|
+
'language the text is written in. Ignore field names in quotes. Nothing else.';
|
|
524
|
+
|
|
525
|
+
export function parrafoDeInstrucciones(brief: string): string {
|
|
526
|
+
const limpio = stripFilesManifest(brief).trim();
|
|
527
|
+
const primero = limpio.split(/\n\s*\n/)[0]!.trim();
|
|
528
|
+
return primero.length >= 12 ? primero : limpio.slice(0, 300);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** El código ISO del idioma en que están escritas las instrucciones, o null si no se pudo saber. */
|
|
532
|
+
export async function idiomaDeLasInstrucciones(brief: string): Promise<string | null> {
|
|
533
|
+
try {
|
|
534
|
+
const cfg = resolverLlm(process.env);
|
|
535
|
+
const r = await llmChat(
|
|
536
|
+
{ ...cfg, timeoutMs: 15_000, maxRetries: 1 },
|
|
537
|
+
{ system: PIDE_EL_IDIOMA, user: parrafoDeInstrucciones(brief) },
|
|
538
|
+
);
|
|
539
|
+
const codigo = r.trim().toLowerCase().replace(/[^a-z]/g, '');
|
|
540
|
+
return /^[a-z]{2}$/.test(codigo) ? codigo : null;
|
|
541
|
+
} catch {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export async function nombreDelTema(brief: string, deReserva: string, idioma?: string | null): Promise<string> {
|
|
547
|
+
// El idioma se decide con el párrafo de las instrucciones, no con el encargo
|
|
548
|
+
// entero: ver `idiomaDeLasInstrucciones`.
|
|
549
|
+
idioma = idioma ?? (await idiomaDeLasInstrucciones(brief));
|
|
465
550
|
try {
|
|
466
551
|
const cfg = resolverLlm(process.env);
|
|
467
552
|
const respuesta = await llmChat(
|
|
@@ -489,9 +574,19 @@ export async function nombreDelTema(brief: string, deReserva: string): Promise<s
|
|
|
489
574
|
// Sin el manifiesto, por lo mismo que en `formatoPedido`: son 1.500
|
|
490
575
|
// caracteres de presupuesto y un hash de 64 ocupa sitio sin decir nada
|
|
491
576
|
// del tema. Con adjuntos cortos llegaba a colarse entero.
|
|
492
|
-
{
|
|
577
|
+
{
|
|
578
|
+
system: PIDE_UN_NOMBRE,
|
|
579
|
+
// Si quien llama ya sabe el idioma de las instrucciones, se le dice: no
|
|
580
|
+
// hay que volver a adivinarlo, que es donde el modelo se equivoca.
|
|
581
|
+
user:
|
|
582
|
+
stripFilesManifest(brief).trim().slice(0, 1_500) +
|
|
583
|
+
(idioma
|
|
584
|
+
? `\n\n(Write the NAME in ${nombreDeIdioma(idioma)}, even if the data or the quoted field names ` +
|
|
585
|
+
`are in another language. Answer LANG: ${idioma}.)`
|
|
586
|
+
: ''),
|
|
587
|
+
},
|
|
493
588
|
);
|
|
494
|
-
const nombre = comoNombre(respuesta);
|
|
589
|
+
const nombre = comoNombre(lineaDelNombre(respuesta));
|
|
495
590
|
if (nombre) return nombre;
|
|
496
591
|
console.warn(`[salida] el modelo no dio un nombre usable; el archivo va como «${deReserva}»`);
|
|
497
592
|
} catch (err) {
|