@luanpoppe/ai 1.1.0 → 1.1.2
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/dist/@types/agent.d.ts +3 -0
- package/dist/@types/agent.d.ts.map +1 -0
- package/dist/@types/agent.js +3 -0
- package/dist/@types/agent.js.map +1 -0
- package/dist/@types/ai-call.d.ts +32 -0
- package/dist/@types/ai-call.d.ts.map +1 -0
- package/dist/@types/ai-call.js +3 -0
- package/dist/@types/ai-call.js.map +1 -0
- package/dist/@types/checkpointers.d.ts +106 -0
- package/dist/@types/checkpointers.d.ts.map +1 -0
- package/dist/@types/checkpointers.js +3 -0
- package/dist/@types/checkpointers.js.map +1 -0
- package/dist/ai.d.ts +49 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +206 -0
- package/dist/ai.js.map +1 -0
- package/dist/index.d.ts +10 -52
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -135
- package/dist/index.js.map +1 -1
- package/dist/langchain/checkpointers.d.ts +86 -0
- package/dist/langchain/checkpointers.d.ts.map +1 -0
- package/dist/langchain/checkpointers.js +242 -0
- package/dist/langchain/checkpointers.js.map +1 -0
- package/package.json +9 -1
- package/src/@types/agent.ts +3 -0
- package/src/@types/ai-call.ts +38 -0
- package/src/@types/checkpointers.ts +117 -0
- package/src/ai.ts +306 -0
- package/src/index.ts +34 -214
- package/src/langchain/checkpointers.ts +331 -0
- package/tests/e2e/ai-retry-fallback.test.ts +213 -0
- package/tests/e2e/ai.test.ts +125 -38
- package/tests/unit/index.test.ts +310 -13
- package/tests/unit/langchain/checkpointers.test.ts +147 -0
- package/tests/unit/langchain/tools.test.ts +2 -1
package/tests/e2e/ai.test.ts
CHANGED
|
@@ -25,12 +25,12 @@ describe("AI E2E Tests", () => {
|
|
|
25
25
|
|
|
26
26
|
const messages = [
|
|
27
27
|
AIMessages.human(
|
|
28
|
-
"Olá! Responda apenas com 'Teste E2E GPT funcionando'"
|
|
28
|
+
"Olá! Responda apenas com 'Teste E2E GPT funcionando'",
|
|
29
29
|
),
|
|
30
30
|
];
|
|
31
31
|
|
|
32
32
|
const result = await ai.call({
|
|
33
|
-
aiModel: "gpt-
|
|
33
|
+
aiModel: "gpt-5-nano",
|
|
34
34
|
messages,
|
|
35
35
|
});
|
|
36
36
|
|
|
@@ -39,7 +39,7 @@ describe("AI E2E Tests", () => {
|
|
|
39
39
|
expect(result.messages).toBeDefined();
|
|
40
40
|
expect(result.messages.length).toBeGreaterThan(0);
|
|
41
41
|
expect(result.text.toLowerCase()).toContain("teste");
|
|
42
|
-
}
|
|
42
|
+
},
|
|
43
43
|
);
|
|
44
44
|
|
|
45
45
|
it(
|
|
@@ -56,7 +56,7 @@ describe("AI E2E Tests", () => {
|
|
|
56
56
|
|
|
57
57
|
const messages = [
|
|
58
58
|
AIMessages.human(
|
|
59
|
-
"Olá! Responda apenas com 'Teste E2E Gemini funcionando'"
|
|
59
|
+
"Olá! Responda apenas com 'Teste E2E Gemini funcionando'",
|
|
60
60
|
),
|
|
61
61
|
];
|
|
62
62
|
|
|
@@ -70,7 +70,7 @@ describe("AI E2E Tests", () => {
|
|
|
70
70
|
expect(result.messages).toBeDefined();
|
|
71
71
|
expect(result.messages.length).toBeGreaterThan(0);
|
|
72
72
|
expect(result.text.toLowerCase()).toContain("teste");
|
|
73
|
-
}
|
|
73
|
+
},
|
|
74
74
|
);
|
|
75
75
|
|
|
76
76
|
it(
|
|
@@ -87,12 +87,12 @@ describe("AI E2E Tests", () => {
|
|
|
87
87
|
|
|
88
88
|
const messages = [
|
|
89
89
|
AIMessages.human(
|
|
90
|
-
"Olá! Responda apenas com 'Teste E2E OpenRouter funcionando'"
|
|
90
|
+
"Olá! Responda apenas com 'Teste E2E OpenRouter funcionando'",
|
|
91
91
|
),
|
|
92
92
|
];
|
|
93
93
|
|
|
94
94
|
const result = await ai.call({
|
|
95
|
-
aiModel: "openrouter/openai/gpt-5",
|
|
95
|
+
aiModel: "openrouter/openai/gpt-5-nano",
|
|
96
96
|
messages,
|
|
97
97
|
});
|
|
98
98
|
|
|
@@ -101,7 +101,7 @@ describe("AI E2E Tests", () => {
|
|
|
101
101
|
expect(result.messages).toBeDefined();
|
|
102
102
|
expect(result.messages.length).toBeGreaterThan(0);
|
|
103
103
|
expect(result.text.toLowerCase()).toContain("teste");
|
|
104
|
-
}
|
|
104
|
+
},
|
|
105
105
|
);
|
|
106
106
|
});
|
|
107
107
|
|
|
@@ -119,21 +119,21 @@ describe("AI E2E Tests", () => {
|
|
|
119
119
|
});
|
|
120
120
|
|
|
121
121
|
const systemMessage = AIMessages.system(
|
|
122
|
-
"Você é um assistente útil que responde de forma concisa."
|
|
122
|
+
"Você é um assistente útil que responde de forma concisa.",
|
|
123
123
|
);
|
|
124
124
|
const humanMessage = AIMessages.human("Qual é a capital do Brasil?");
|
|
125
125
|
|
|
126
126
|
const messages = [systemMessage, humanMessage];
|
|
127
127
|
|
|
128
128
|
const result = await ai.call({
|
|
129
|
-
aiModel: "gpt-
|
|
129
|
+
aiModel: "gpt-5-nano",
|
|
130
130
|
messages,
|
|
131
131
|
});
|
|
132
132
|
|
|
133
133
|
expect(result.text).toBeDefined();
|
|
134
134
|
expect(result.text.length).toBeGreaterThan(0);
|
|
135
135
|
expect(result.text.toLowerCase()).toContain("brasília");
|
|
136
|
-
}
|
|
136
|
+
},
|
|
137
137
|
);
|
|
138
138
|
|
|
139
139
|
it(
|
|
@@ -161,7 +161,7 @@ describe("AI E2E Tests", () => {
|
|
|
161
161
|
expect(result.text).toBeDefined();
|
|
162
162
|
expect(result.text.length).toBeGreaterThan(0);
|
|
163
163
|
expect(result.text).toMatch(/\b4\b/);
|
|
164
|
-
}
|
|
164
|
+
},
|
|
165
165
|
);
|
|
166
166
|
|
|
167
167
|
it(
|
|
@@ -177,14 +177,14 @@ describe("AI E2E Tests", () => {
|
|
|
177
177
|
});
|
|
178
178
|
|
|
179
179
|
const systemMessage = AIMessages.system(
|
|
180
|
-
"Você é um assistente útil que responde de forma concisa."
|
|
180
|
+
"Você é um assistente útil que responde de forma concisa.",
|
|
181
181
|
);
|
|
182
182
|
const humanMessage = AIMessages.human("Qual é a capital da França?");
|
|
183
183
|
|
|
184
184
|
const messages = [systemMessage, humanMessage];
|
|
185
185
|
|
|
186
186
|
const result = await ai.call({
|
|
187
|
-
aiModel: "openrouter/openai/gpt-5",
|
|
187
|
+
aiModel: "openrouter/openai/gpt-5-nano",
|
|
188
188
|
messages,
|
|
189
189
|
});
|
|
190
190
|
|
|
@@ -194,9 +194,9 @@ describe("AI E2E Tests", () => {
|
|
|
194
194
|
expect(
|
|
195
195
|
lowerText.includes("paris") ||
|
|
196
196
|
lowerText.includes("paris,") ||
|
|
197
|
-
lowerText.includes("paris.")
|
|
197
|
+
lowerText.includes("paris."),
|
|
198
198
|
).toBe(true);
|
|
199
|
-
}
|
|
199
|
+
},
|
|
200
200
|
);
|
|
201
201
|
});
|
|
202
202
|
|
|
@@ -215,7 +215,7 @@ describe("AI E2E Tests", () => {
|
|
|
215
215
|
];
|
|
216
216
|
|
|
217
217
|
const result = await ai.call({
|
|
218
|
-
aiModel: "gpt-
|
|
218
|
+
aiModel: "gpt-5-nano",
|
|
219
219
|
messages,
|
|
220
220
|
systemPrompt:
|
|
221
221
|
"Você é um especialista em programação. Responda sempre em português brasileiro.",
|
|
@@ -232,7 +232,7 @@ describe("AI E2E Tests", () => {
|
|
|
232
232
|
lowerText.includes("code") ||
|
|
233
233
|
lowerText.includes("software") ||
|
|
234
234
|
lowerText.includes("linguagem") ||
|
|
235
|
-
lowerText.includes("desenvolvimento")
|
|
235
|
+
lowerText.includes("desenvolvimento"),
|
|
236
236
|
).toBe(true);
|
|
237
237
|
});
|
|
238
238
|
|
|
@@ -253,7 +253,7 @@ describe("AI E2E Tests", () => {
|
|
|
253
253
|
];
|
|
254
254
|
|
|
255
255
|
const result = await ai.call({
|
|
256
|
-
aiModel: "openrouter/openai/gpt-5",
|
|
256
|
+
aiModel: "openrouter/openai/gpt-5-nano",
|
|
257
257
|
messages,
|
|
258
258
|
systemPrompt:
|
|
259
259
|
"Você é um cientista. Responda sempre em português brasileiro.",
|
|
@@ -303,7 +303,7 @@ describe("AI E2E Tests", () => {
|
|
|
303
303
|
// Se não encontrou palavras específicas de ciência, pelo menos verifica que a resposta existe
|
|
304
304
|
expect(result.text.length).toBeGreaterThan(10);
|
|
305
305
|
// Se encontrou palavras relacionadas a ciência, ótimo, mas não é obrigatório para passar o teste
|
|
306
|
-
}
|
|
306
|
+
},
|
|
307
307
|
);
|
|
308
308
|
});
|
|
309
309
|
|
|
@@ -328,12 +328,12 @@ describe("AI E2E Tests", () => {
|
|
|
328
328
|
|
|
329
329
|
const messages = [
|
|
330
330
|
AIMessages.human(
|
|
331
|
-
"Crie uma pessoa fictícia. Nome: João, Idade: 30, Cidade: São Paulo"
|
|
331
|
+
"Crie uma pessoa fictícia. Nome: João, Idade: 30, Cidade: São Paulo",
|
|
332
332
|
),
|
|
333
333
|
];
|
|
334
334
|
|
|
335
335
|
const result = await ai.callStructuredOutput({
|
|
336
|
-
aiModel: "gpt-
|
|
336
|
+
aiModel: "gpt-5-nano",
|
|
337
337
|
messages,
|
|
338
338
|
outputSchema,
|
|
339
339
|
});
|
|
@@ -342,7 +342,7 @@ describe("AI E2E Tests", () => {
|
|
|
342
342
|
expect(result.response.name).toBe("João");
|
|
343
343
|
expect(result.response.age).toBe(30);
|
|
344
344
|
expect(result.response.city).toBe("São Paulo");
|
|
345
|
-
}
|
|
345
|
+
},
|
|
346
346
|
);
|
|
347
347
|
|
|
348
348
|
it(
|
|
@@ -364,7 +364,7 @@ describe("AI E2E Tests", () => {
|
|
|
364
364
|
|
|
365
365
|
const messages = [
|
|
366
366
|
AIMessages.human(
|
|
367
|
-
"Calcule a soma e o produto de 5 e 3. A soma de 5 e 3 é 8, e o produto é 15."
|
|
367
|
+
"Calcule a soma e o produto de 5 e 3. A soma de 5 e 3 é 8, e o produto é 15.",
|
|
368
368
|
),
|
|
369
369
|
];
|
|
370
370
|
|
|
@@ -380,7 +380,7 @@ describe("AI E2E Tests", () => {
|
|
|
380
380
|
expect(result.response.sum).toBeLessThanOrEqual(9);
|
|
381
381
|
expect(result.response.product).toBeGreaterThanOrEqual(14);
|
|
382
382
|
expect(result.response.product).toBeLessThanOrEqual(16);
|
|
383
|
-
}
|
|
383
|
+
},
|
|
384
384
|
);
|
|
385
385
|
|
|
386
386
|
it(
|
|
@@ -405,7 +405,7 @@ describe("AI E2E Tests", () => {
|
|
|
405
405
|
];
|
|
406
406
|
|
|
407
407
|
const result = await ai.callStructuredOutput({
|
|
408
|
-
aiModel: "openrouter/openai/gpt-5",
|
|
408
|
+
aiModel: "openrouter/openai/gpt-5-nano",
|
|
409
409
|
messages,
|
|
410
410
|
outputSchema,
|
|
411
411
|
});
|
|
@@ -413,7 +413,7 @@ describe("AI E2E Tests", () => {
|
|
|
413
413
|
expect(result.response).toBeDefined();
|
|
414
414
|
expect(result.response.sum).toBe(11);
|
|
415
415
|
expect(result.response.product).toBe(28);
|
|
416
|
-
}
|
|
416
|
+
},
|
|
417
417
|
);
|
|
418
418
|
|
|
419
419
|
it(
|
|
@@ -441,12 +441,12 @@ describe("AI E2E Tests", () => {
|
|
|
441
441
|
|
|
442
442
|
const messages = [
|
|
443
443
|
AIMessages.human(
|
|
444
|
-
"Crie um prontuário médico formal para um paciente chamado João, 30 anos, com diagnóstico de gripe. A data da consulta foi 25/01/2026."
|
|
444
|
+
"Crie um prontuário médico formal para um paciente chamado João, 30 anos, com diagnóstico de gripe. A data da consulta foi 25/01/2026.",
|
|
445
445
|
),
|
|
446
446
|
];
|
|
447
447
|
|
|
448
448
|
const result = await ai.callStructuredOutput({
|
|
449
|
-
aiModel: "openrouter/openai/gpt-5",
|
|
449
|
+
aiModel: "openrouter/openai/gpt-5-nano",
|
|
450
450
|
messages,
|
|
451
451
|
outputSchema,
|
|
452
452
|
});
|
|
@@ -460,7 +460,7 @@ describe("AI E2E Tests", () => {
|
|
|
460
460
|
if (result.response.dataConsulta) {
|
|
461
461
|
expect(typeof result.response.dataConsulta).toBe("string");
|
|
462
462
|
}
|
|
463
|
-
}
|
|
463
|
+
},
|
|
464
464
|
);
|
|
465
465
|
});
|
|
466
466
|
|
|
@@ -482,7 +482,7 @@ describe("AI E2E Tests", () => {
|
|
|
482
482
|
];
|
|
483
483
|
|
|
484
484
|
const result = await ai.call({
|
|
485
|
-
aiModel: "gpt-
|
|
485
|
+
aiModel: "gpt-5-nano",
|
|
486
486
|
messages,
|
|
487
487
|
modelConfig: {
|
|
488
488
|
maxTokens: 50,
|
|
@@ -493,7 +493,7 @@ describe("AI E2E Tests", () => {
|
|
|
493
493
|
expect(result.text).toBeDefined();
|
|
494
494
|
// maxTokens pode não ser exatamente respeitado, então verificamos apenas que existe resposta
|
|
495
495
|
expect(result.text.length).toBeGreaterThan(0);
|
|
496
|
-
}
|
|
496
|
+
},
|
|
497
497
|
);
|
|
498
498
|
|
|
499
499
|
it(
|
|
@@ -524,17 +524,104 @@ describe("AI E2E Tests", () => {
|
|
|
524
524
|
|
|
525
525
|
if (result.text === "Empty response from the model") {
|
|
526
526
|
throw new Error(
|
|
527
|
-
"OpenRouter retornou resposta vazia para maxTokens/temperature"
|
|
527
|
+
"OpenRouter retornou resposta vazia para maxTokens/temperature",
|
|
528
528
|
);
|
|
529
529
|
}
|
|
530
530
|
expect(
|
|
531
531
|
(result.messages.at(-1)?.response_metadata?.tokenUsage as any)
|
|
532
|
-
?.totalTokens
|
|
532
|
+
?.totalTokens,
|
|
533
533
|
).toBeLessThanOrEqual(maxTokens);
|
|
534
534
|
expect(result.text).toBeDefined();
|
|
535
535
|
// maxTokens pode não ser exatamente respeitado, então verificamos apenas que existe resposta
|
|
536
536
|
expect(result.text.length).toBeGreaterThan(0);
|
|
537
|
-
}
|
|
537
|
+
},
|
|
538
|
+
);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
describe("Persistência de histórico (memory/checkpointer)", () => {
|
|
542
|
+
it(
|
|
543
|
+
"deve manter histórico entre chamadas com threadId e MemorySaver",
|
|
544
|
+
{ timeout },
|
|
545
|
+
async () => {
|
|
546
|
+
if (!openAIApiKey) {
|
|
547
|
+
console.log("OPENAI_API_KEY não está configurada");
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const ai = new AI({
|
|
551
|
+
openAIApiKey: openAIApiKey!,
|
|
552
|
+
memory: { type: "memory" },
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
const threadId = "e2e-memory-thread-1";
|
|
556
|
+
|
|
557
|
+
// Primeira mensagem: informar nome
|
|
558
|
+
await ai.call({
|
|
559
|
+
aiModel: "gpt-5-nano",
|
|
560
|
+
messages: [AIMessages.human("Meu nome é Carlos. Lembre-se disso.")],
|
|
561
|
+
threadId,
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
// Segunda mensagem: perguntar o nome (deve lembrar do contexto)
|
|
565
|
+
const result = await ai.call({
|
|
566
|
+
aiModel: "gpt-5-nano",
|
|
567
|
+
messages: [AIMessages.human("Qual é o meu nome?")],
|
|
568
|
+
threadId,
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
expect(result.text).toBeDefined();
|
|
572
|
+
expect(result.text.toLowerCase()).toContain("carlos");
|
|
573
|
+
},
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
it(
|
|
577
|
+
"deve retornar histórico de mensagens via AIMemory.getHistory",
|
|
578
|
+
{ timeout },
|
|
579
|
+
async () => {
|
|
580
|
+
if (!openAIApiKey) {
|
|
581
|
+
console.log("OPENAI_API_KEY não está configurada");
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
const ai = new AI({
|
|
585
|
+
openAIApiKey: openAIApiKey!,
|
|
586
|
+
memory: { type: "memory" },
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
const threadId = "e2e-aimemory-history-thread";
|
|
590
|
+
|
|
591
|
+
// Primeira mensagem
|
|
592
|
+
await ai.call({
|
|
593
|
+
aiModel: "gpt-5-nano",
|
|
594
|
+
messages: [AIMessages.human("Diga apenas: ok, recebi.")],
|
|
595
|
+
threadId,
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
// Segunda mensagem
|
|
599
|
+
await ai.call({
|
|
600
|
+
aiModel: "gpt-5-nano",
|
|
601
|
+
messages: [AIMessages.human("O que eu disse na mensagem anterior?")],
|
|
602
|
+
threadId,
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
// Buscar histórico via ai.memory.getHistory (agent definido em getRawAgent)
|
|
606
|
+
|
|
607
|
+
const { fullHistory, messages } = await ai.memory.getHistory(threadId);
|
|
608
|
+
|
|
609
|
+
console.log({ messages });
|
|
610
|
+
|
|
611
|
+
expect(fullHistory.length).toBeGreaterThan(0);
|
|
612
|
+
expect(messages.length).toBeGreaterThanOrEqual(2);
|
|
613
|
+
|
|
614
|
+
const allContent = messages.map((m) => m.content).join(" ");
|
|
615
|
+
expect(allContent).toContain("Diga apenas");
|
|
616
|
+
expect(allContent).toContain("O que eu disse");
|
|
617
|
+
|
|
618
|
+
expect(
|
|
619
|
+
messages.every((m) => ["human", "ai", "tool"].includes(m.role)),
|
|
620
|
+
).toBe(true);
|
|
621
|
+
expect(messages.every((m) => typeof m.createdAt === "string")).toBe(
|
|
622
|
+
true,
|
|
623
|
+
);
|
|
624
|
+
},
|
|
538
625
|
);
|
|
539
626
|
});
|
|
540
627
|
|
|
@@ -555,7 +642,7 @@ describe("AI E2E Tests", () => {
|
|
|
555
642
|
];
|
|
556
643
|
|
|
557
644
|
const result = await ai.call({
|
|
558
|
-
aiModel: "gpt-
|
|
645
|
+
aiModel: "gpt-5-nano",
|
|
559
646
|
messages,
|
|
560
647
|
});
|
|
561
648
|
|
|
@@ -582,13 +669,13 @@ describe("AI E2E Tests", () => {
|
|
|
582
669
|
];
|
|
583
670
|
|
|
584
671
|
const result = await ai.call({
|
|
585
|
-
aiModel: "openrouter/openai/gpt-5",
|
|
672
|
+
aiModel: "openrouter/openai/gpt-5-nano",
|
|
586
673
|
messages,
|
|
587
674
|
});
|
|
588
675
|
|
|
589
676
|
expect(result.text).toBeDefined();
|
|
590
677
|
expect(result.text.toLowerCase()).toContain("joão");
|
|
591
|
-
}
|
|
678
|
+
},
|
|
592
679
|
);
|
|
593
680
|
});
|
|
594
681
|
});
|