@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
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { StateSnapshot } from "@langchain/langgraph";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interface mínima para um grafo compilado com checkpointer.
|
|
5
|
+
* Usado para acessar o histórico de conversas via getStateHistory.
|
|
6
|
+
*
|
|
7
|
+
* @see https://docs.langchain.com/oss/javascript/langgraph/persistence#get-state-history
|
|
8
|
+
* @see https://docs.langchain.com/oss/javascript/langgraph/add-memory#manage-checkpoints
|
|
9
|
+
*/
|
|
10
|
+
export interface GraphWithStateHistory {
|
|
11
|
+
/**
|
|
12
|
+
* Retorna o histórico completo de checkpoints de uma thread.
|
|
13
|
+
* Ordenado cronologicamente (mais recente primeiro).
|
|
14
|
+
*/
|
|
15
|
+
getStateHistory(config: {
|
|
16
|
+
configurable: { thread_id: string; checkpoint_id?: string };
|
|
17
|
+
}): AsyncIterable<StateSnapshot>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Retorna o estado atual (último checkpoint) da thread.
|
|
21
|
+
*/
|
|
22
|
+
getState?(config: {
|
|
23
|
+
configurable: { thread_id: string; checkpoint_id?: string };
|
|
24
|
+
}): Promise<StateSnapshot>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Configuração para checkpointer em memória (desenvolvimento/testes).
|
|
29
|
+
*/
|
|
30
|
+
export type MemoryCheckpointerConfig = {
|
|
31
|
+
type: "memory";
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Configuração para checkpointer SQLite.
|
|
36
|
+
* Use ":memory:" para testes ou path para arquivo persistente.
|
|
37
|
+
*/
|
|
38
|
+
export type SqliteCheckpointerConfig = {
|
|
39
|
+
type: "sqlite";
|
|
40
|
+
connectionString: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Configuração para checkpointer Postgres.
|
|
45
|
+
*/
|
|
46
|
+
export type PostgresCheckpointerConfig = {
|
|
47
|
+
type: "postgres";
|
|
48
|
+
connectionString: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Configuração para checkpointer Redis.
|
|
53
|
+
* Requer Redis 8+ ou Redis Stack (RedisJSON, RediSearch).
|
|
54
|
+
*/
|
|
55
|
+
export type RedisCheckpointerConfig = {
|
|
56
|
+
type: "redis";
|
|
57
|
+
url: string;
|
|
58
|
+
options?: {
|
|
59
|
+
defaultTTL?: number;
|
|
60
|
+
refreshOnRead?: boolean;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Configuração para checkpointer MongoDB com cliente existente.
|
|
66
|
+
*/
|
|
67
|
+
export type MongoDBCheckpointerConfigWithClient = {
|
|
68
|
+
type: "mongodb";
|
|
69
|
+
client: { close?: () => Promise<void> };
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Configuração para checkpointer MongoDB com URL.
|
|
74
|
+
*/
|
|
75
|
+
export type MongoDBCheckpointerConfigWithUrl = {
|
|
76
|
+
type: "mongodb";
|
|
77
|
+
url: string;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type MongoDBCheckpointerConfig =
|
|
81
|
+
| MongoDBCheckpointerConfigWithClient
|
|
82
|
+
| MongoDBCheckpointerConfigWithUrl;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Configuração para persistência de histórico de conversas.
|
|
86
|
+
* Permite escolher o backend de armazenamento.
|
|
87
|
+
*/
|
|
88
|
+
export type MemoryConfig =
|
|
89
|
+
| MemoryCheckpointerConfig
|
|
90
|
+
| SqliteCheckpointerConfig
|
|
91
|
+
| PostgresCheckpointerConfig
|
|
92
|
+
| RedisCheckpointerConfig
|
|
93
|
+
| MongoDBCheckpointerConfig;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Tipo de mensagem no histórico (Human, AI ou Tool).
|
|
97
|
+
*/
|
|
98
|
+
export type MessageRole = "human" | "ai" | "tool";
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Item de mensagem extraído do histórico, com role, horário e conteúdo.
|
|
102
|
+
*/
|
|
103
|
+
export type HistoryMessageItem = {
|
|
104
|
+
role: MessageRole;
|
|
105
|
+
createdAt: string;
|
|
106
|
+
content: string;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Retorno do método getHistory com histórico completo e lista de mensagens.
|
|
111
|
+
*/
|
|
112
|
+
export type GetHistoryResult = {
|
|
113
|
+
/** Histórico completo de checkpoints (mais recente primeiro). */
|
|
114
|
+
fullHistory: StateSnapshot[];
|
|
115
|
+
/** Lista de mensagens em ordem cronológica, com role, horário e conteúdo. */
|
|
116
|
+
messages: HistoryMessageItem[];
|
|
117
|
+
};
|
package/src/ai.ts
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { AIModels, LLMModelConfig } from "./langchain/models";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { createAgent, modelRetryMiddleware } from "langchain";
|
|
4
|
+
import type { AIAgent } from "./@types/agent";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
AIMemory,
|
|
8
|
+
type BaseCheckpointSaver,
|
|
9
|
+
type MemoryConfig,
|
|
10
|
+
} from "./langchain/checkpointers";
|
|
11
|
+
import type { AIModelNames } from "./@types/model-names";
|
|
12
|
+
import type {
|
|
13
|
+
AICallParams,
|
|
14
|
+
AICallReturn,
|
|
15
|
+
AICallStructuredOutputParams,
|
|
16
|
+
AICallStructuredOutputReturn,
|
|
17
|
+
} from "./@types/ai-call";
|
|
18
|
+
|
|
19
|
+
export type {
|
|
20
|
+
AICallParams,
|
|
21
|
+
AICallReturn,
|
|
22
|
+
AICallStructuredOutputParams,
|
|
23
|
+
AICallStructuredOutputReturn,
|
|
24
|
+
} from "./@types/ai-call";
|
|
25
|
+
|
|
26
|
+
type AIConstructor = {
|
|
27
|
+
googleGeminiToken?: string;
|
|
28
|
+
openAIApiKey?: string;
|
|
29
|
+
openRouterApiKey?: string;
|
|
30
|
+
/** Lista padrão de modelos de fallback (usada em call/callStructuredOutput quando não passada no método) */
|
|
31
|
+
aiModelsFallback?: AIModelNames[];
|
|
32
|
+
/** Configuração de persistência de histórico (memory, sqlite, postgres, redis, mongodb) ou instância AIMemory */
|
|
33
|
+
memory?: MemoryConfig | AIMemory;
|
|
34
|
+
/** Instância de checkpointer para usuários avançados (alternativa a memory) */
|
|
35
|
+
checkpointer?: BaseCheckpointSaver;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export class AI {
|
|
39
|
+
private _memory: AIMemory | undefined;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Instância de AIMemory. Lança exceção se memory não estiver configurado.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* const { fullHistory, messages } = await ai.memory.getHistory(threadId);
|
|
46
|
+
*/
|
|
47
|
+
get memory(): AIMemory {
|
|
48
|
+
if (!this._memory) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
"memory não está configurado. Passe memory no construtor do AI (ex: memory: { type: 'memory' }).",
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return this._memory;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private checkpointer: BaseCheckpointSaver | undefined;
|
|
57
|
+
private checkpointerPromise: Promise<BaseCheckpointSaver> | undefined;
|
|
58
|
+
|
|
59
|
+
constructor(private config: AIConstructor) {
|
|
60
|
+
if (config.checkpointer) {
|
|
61
|
+
this.checkpointer = config.checkpointer;
|
|
62
|
+
}
|
|
63
|
+
if (config.memory) {
|
|
64
|
+
this._memory =
|
|
65
|
+
config.memory instanceof AIMemory
|
|
66
|
+
? config.memory
|
|
67
|
+
: new AIMemory(config.memory);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private async getCheckpointer(): Promise<BaseCheckpointSaver | undefined> {
|
|
72
|
+
if (this.checkpointer) return this.checkpointer;
|
|
73
|
+
if (this._memory) {
|
|
74
|
+
if (!this.checkpointerPromise) {
|
|
75
|
+
this.checkpointerPromise = this._memory.getCheckpointer();
|
|
76
|
+
}
|
|
77
|
+
this.checkpointer = await this.checkpointerPromise;
|
|
78
|
+
return this.checkpointer;
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private ensureThreadIdWhenCheckpointer(params: AICallParams): void {
|
|
84
|
+
if (this.config.checkpointer || this.config.memory !== undefined) {
|
|
85
|
+
if (!params.threadId) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
"threadId é obrigatório quando memory ou checkpointer está configurado. " +
|
|
88
|
+
"Passe threadId em AICallParams para identificar a conversa.",
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private async invokeWithRetryAndFallback<T>(
|
|
95
|
+
params: AICallParams,
|
|
96
|
+
createAgentForModel: (paramsForModel: AICallParams) => AIAgent,
|
|
97
|
+
execute: (agent: AIAgent) => Promise<T>,
|
|
98
|
+
): Promise<{ result: T; agent: AIAgent }> {
|
|
99
|
+
const fallback =
|
|
100
|
+
params.aiModelsFallback ?? this.config.aiModelsFallback ?? [];
|
|
101
|
+
const models: (typeof params.aiModel)[] = [params.aiModel, ...fallback];
|
|
102
|
+
let lastError: unknown;
|
|
103
|
+
|
|
104
|
+
for (const aiModel of models) {
|
|
105
|
+
const paramsForModel = { ...params, aiModel };
|
|
106
|
+
const agent = createAgentForModel(paramsForModel);
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const result = await execute(agent);
|
|
110
|
+
return { result, agent };
|
|
111
|
+
} catch (error) {
|
|
112
|
+
lastError = error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
throw lastError;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async call(params: AICallParams): AICallReturn {
|
|
120
|
+
const { messages } = params;
|
|
121
|
+
|
|
122
|
+
this.ensureThreadIdWhenCheckpointer(params);
|
|
123
|
+
const checkpointer = await this.getCheckpointer();
|
|
124
|
+
|
|
125
|
+
const invokeConfig =
|
|
126
|
+
params.threadId && checkpointer
|
|
127
|
+
? { configurable: { thread_id: params.threadId } }
|
|
128
|
+
: undefined;
|
|
129
|
+
|
|
130
|
+
const { result: response, agent } = await this.invokeWithRetryAndFallback(
|
|
131
|
+
params,
|
|
132
|
+
(paramsForModel) =>
|
|
133
|
+
createAgent({
|
|
134
|
+
...this.standardAgent(paramsForModel, checkpointer),
|
|
135
|
+
}),
|
|
136
|
+
(agent) => agent.invoke({ messages }, invokeConfig as any),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
this._memory?.setAgent(agent);
|
|
140
|
+
|
|
141
|
+
const rawContent = response.messages.at(-1)?.content as string | undefined;
|
|
142
|
+
const text =
|
|
143
|
+
typeof rawContent === "string" && rawContent.trim()
|
|
144
|
+
? rawContent
|
|
145
|
+
: "Empty response from the model";
|
|
146
|
+
return {
|
|
147
|
+
text,
|
|
148
|
+
messages: response.messages,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async callStructuredOutput<T extends z.ZodSchema>(
|
|
153
|
+
params: AICallStructuredOutputParams<T>,
|
|
154
|
+
): AICallStructuredOutputReturn<typeof params.outputSchema> {
|
|
155
|
+
const { outputSchema, messages } = params;
|
|
156
|
+
|
|
157
|
+
this.ensureThreadIdWhenCheckpointer(params);
|
|
158
|
+
const checkpointer = await this.getCheckpointer();
|
|
159
|
+
|
|
160
|
+
const invokeConfig =
|
|
161
|
+
params.threadId && checkpointer
|
|
162
|
+
? { configurable: { thread_id: params.threadId } }
|
|
163
|
+
: undefined;
|
|
164
|
+
|
|
165
|
+
const { result: response, agent } = await this.invokeWithRetryAndFallback(
|
|
166
|
+
params,
|
|
167
|
+
(paramsForModel) =>
|
|
168
|
+
createAgent({
|
|
169
|
+
...this.standardAgent(paramsForModel, checkpointer),
|
|
170
|
+
responseFormat: this.normalizeSchemaForOpenAI(
|
|
171
|
+
outputSchema,
|
|
172
|
+
paramsForModel.aiModel,
|
|
173
|
+
) as any,
|
|
174
|
+
}),
|
|
175
|
+
(agent) => agent.invoke({ messages }, invokeConfig as any),
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
this._memory?.setAgent(agent);
|
|
179
|
+
|
|
180
|
+
const parsedResponse = outputSchema.parse(response?.structuredResponse);
|
|
181
|
+
|
|
182
|
+
return { response: parsedResponse };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Normaliza schemas Zod para compatibilidade com OpenAI/OpenRouter
|
|
187
|
+
* OpenAI exige que todos os campos em properties estejam no array required
|
|
188
|
+
* quando usa response_format: 'extract'
|
|
189
|
+
*/
|
|
190
|
+
private normalizeSchemaForOpenAI<T extends z.ZodSchema>(
|
|
191
|
+
schema: T,
|
|
192
|
+
aiModel: string,
|
|
193
|
+
): z.ZodSchema {
|
|
194
|
+
// Apenas normaliza para modelos OpenAI/OpenRouter
|
|
195
|
+
const isOpenAIModel =
|
|
196
|
+
aiModel.startsWith("gpt") || aiModel.startsWith("openrouter/openai/");
|
|
197
|
+
|
|
198
|
+
if (!isOpenAIModel) {
|
|
199
|
+
return schema;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Se o schema é um objeto Zod, precisamos normalizar campos opcionais
|
|
203
|
+
if (schema instanceof z.ZodObject) {
|
|
204
|
+
const shape = schema.shape;
|
|
205
|
+
const newShape: Record<string, z.ZodTypeAny> = {};
|
|
206
|
+
|
|
207
|
+
// Converte campos opcionais para nullable para compatibilidade com OpenAI
|
|
208
|
+
// OpenAI requer que todos os campos estejam no array required
|
|
209
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
210
|
+
if (value instanceof z.ZodOptional) {
|
|
211
|
+
// Converte .optional() para .nullable() para compatibilidade com OpenAI
|
|
212
|
+
const innerType = value._def.innerType as z.ZodTypeAny;
|
|
213
|
+
// Usa z.union para criar um tipo nullable
|
|
214
|
+
newShape[key] = z.union([innerType, z.null()]);
|
|
215
|
+
} else {
|
|
216
|
+
newShape[key] = value;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return z.object(newShape);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return schema;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async getRawAgent(
|
|
227
|
+
params: AICallParams,
|
|
228
|
+
outputSchema?: z.ZodSchema | undefined,
|
|
229
|
+
): Promise<{ agent: AIAgent }> {
|
|
230
|
+
this.ensureThreadIdWhenCheckpointer(params);
|
|
231
|
+
const checkpointer = await this.getCheckpointer();
|
|
232
|
+
|
|
233
|
+
const agent = createAgent({
|
|
234
|
+
...this.standardAgent(params, checkpointer),
|
|
235
|
+
responseFormat: outputSchema as any,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
this._memory?.setAgent(agent);
|
|
239
|
+
|
|
240
|
+
return { agent };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private getModel(params: AICallParams) {
|
|
244
|
+
const { aiModel, modelConfig } = params;
|
|
245
|
+
|
|
246
|
+
const config: LLMModelConfig = {
|
|
247
|
+
model: aiModel,
|
|
248
|
+
maxTokens: modelConfig?.maxTokens,
|
|
249
|
+
temperature: modelConfig?.temperature,
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
if (aiModel.startsWith("gpt")) {
|
|
253
|
+
config.apiKey = this.config.openAIApiKey;
|
|
254
|
+
|
|
255
|
+
return AIModels.gpt(config);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (aiModel.startsWith("gemini")) {
|
|
259
|
+
config.apiKey = this.config.googleGeminiToken;
|
|
260
|
+
|
|
261
|
+
return AIModels.gemini(config);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (aiModel.startsWith("openrouter/")) {
|
|
265
|
+
const modelName = aiModel.replace(/^openrouter\//, "");
|
|
266
|
+
return AIModels.openrouter({
|
|
267
|
+
...config,
|
|
268
|
+
model: modelName,
|
|
269
|
+
apiKey: this.config.openRouterApiKey,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
throw new Error("Model not supported");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private standardAgent(
|
|
277
|
+
params: AICallParams,
|
|
278
|
+
checkpointer?: BaseCheckpointSaver,
|
|
279
|
+
): Parameters<typeof createAgent>[0] {
|
|
280
|
+
const { systemPrompt, maxRetries = 3 } = params;
|
|
281
|
+
|
|
282
|
+
const model = this.getModel(params);
|
|
283
|
+
return {
|
|
284
|
+
model,
|
|
285
|
+
systemPrompt: systemPrompt ?? "",
|
|
286
|
+
middleware: [
|
|
287
|
+
...this.standardMiddlewares(maxRetries),
|
|
288
|
+
...(params.agent?.middleware ?? []),
|
|
289
|
+
],
|
|
290
|
+
tools: params.agent?.tools ?? [],
|
|
291
|
+
responseFormat: undefined as any,
|
|
292
|
+
...(checkpointer && { checkpointer }),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private standardMiddlewares(maxRetries: number) {
|
|
297
|
+
return [
|
|
298
|
+
modelRetryMiddleware({
|
|
299
|
+
maxRetries,
|
|
300
|
+
backoffFactor: 2.0,
|
|
301
|
+
initialDelayMs: 1000,
|
|
302
|
+
onFailure: "error",
|
|
303
|
+
}),
|
|
304
|
+
];
|
|
305
|
+
}
|
|
306
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,218 +1,38 @@
|
|
|
1
|
-
import { AIModels, LLMModelConfig } from "./langchain/models";
|
|
2
|
-
import { AIModelNames } from "./@types/model-names";
|
|
3
1
|
import z from "zod";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
messages: BaseMessage[];
|
|
39
|
-
}>;
|
|
40
|
-
|
|
41
|
-
export type AICallStructuredOutputParams<T extends z.ZodSchema> =
|
|
42
|
-
AICallParams & {
|
|
43
|
-
outputSchema: T;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export type AICallStructuredOutputReturn<T> = Promise<{
|
|
47
|
-
response: z.infer<T>;
|
|
48
|
-
}>;
|
|
49
|
-
|
|
50
|
-
export class AI {
|
|
51
|
-
constructor(private tokens: AIConstructor) {}
|
|
52
|
-
|
|
53
|
-
async call(params: AICallParams): AICallReturn {
|
|
54
|
-
const { messages } = params;
|
|
55
|
-
|
|
56
|
-
const agent = createAgent({
|
|
57
|
-
...this.standardAgent(params),
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const response = await agent.invoke({ messages });
|
|
61
|
-
|
|
62
|
-
const rawContent = response.messages.at(-1)?.content as string | undefined;
|
|
63
|
-
const text =
|
|
64
|
-
typeof rawContent === "string" && rawContent.trim()
|
|
65
|
-
? rawContent
|
|
66
|
-
: "Empty response from the model";
|
|
67
|
-
return {
|
|
68
|
-
text,
|
|
69
|
-
messages: response.messages,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async callStructuredOutput<T extends z.ZodSchema>(
|
|
74
|
-
params: AICallStructuredOutputParams<T>
|
|
75
|
-
): AICallStructuredOutputReturn<typeof params.outputSchema> {
|
|
76
|
-
const { outputSchema, messages, aiModel } = params;
|
|
77
|
-
|
|
78
|
-
// Normaliza o schema para compatibilidade com OpenAI/OpenRouter
|
|
79
|
-
// OpenAI exige que todos os campos em properties estejam no array required
|
|
80
|
-
const normalizedSchema = this.normalizeSchemaForOpenAI(
|
|
81
|
-
outputSchema,
|
|
82
|
-
aiModel
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
const agent = createAgent({
|
|
86
|
-
...this.standardAgent(params),
|
|
87
|
-
responseFormat: normalizedSchema as any,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const response = await agent.invoke({
|
|
91
|
-
messages,
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const parsedResponse = outputSchema.parse(response?.structuredResponse);
|
|
95
|
-
|
|
96
|
-
return { response: parsedResponse };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Normaliza schemas Zod para compatibilidade com OpenAI/OpenRouter
|
|
101
|
-
* OpenAI exige que todos os campos em properties estejam no array required
|
|
102
|
-
* quando usa response_format: 'extract'
|
|
103
|
-
*/
|
|
104
|
-
private normalizeSchemaForOpenAI<T extends z.ZodSchema>(
|
|
105
|
-
schema: T,
|
|
106
|
-
aiModel: string
|
|
107
|
-
): z.ZodSchema {
|
|
108
|
-
// Apenas normaliza para modelos OpenAI/OpenRouter
|
|
109
|
-
const isOpenAIModel =
|
|
110
|
-
aiModel.startsWith("gpt") || aiModel.startsWith("openrouter/openai/");
|
|
111
|
-
|
|
112
|
-
if (!isOpenAIModel) {
|
|
113
|
-
return schema;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Se o schema é um objeto Zod, precisamos normalizar campos opcionais
|
|
117
|
-
if (schema instanceof z.ZodObject) {
|
|
118
|
-
const shape = schema.shape;
|
|
119
|
-
const newShape: Record<string, z.ZodTypeAny> = {};
|
|
120
|
-
|
|
121
|
-
// Converte campos opcionais para nullable para compatibilidade com OpenAI
|
|
122
|
-
// OpenAI requer que todos os campos estejam no array required
|
|
123
|
-
for (const [key, value] of Object.entries(shape)) {
|
|
124
|
-
if (value instanceof z.ZodOptional) {
|
|
125
|
-
// Converte .optional() para .nullable() para compatibilidade com OpenAI
|
|
126
|
-
const innerType = value._def.innerType as z.ZodTypeAny;
|
|
127
|
-
// Usa z.union para criar um tipo nullable
|
|
128
|
-
newShape[key] = z.union([innerType, z.null()]);
|
|
129
|
-
} else {
|
|
130
|
-
newShape[key] = value;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return z.object(newShape);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return schema;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
getRawAgent(
|
|
141
|
-
params: AICallParams,
|
|
142
|
-
outputSchema?: z.ZodSchema | undefined
|
|
143
|
-
): { agent: ReturnType<typeof createAgent> } {
|
|
144
|
-
const agent = createAgent({
|
|
145
|
-
...this.standardAgent(params),
|
|
146
|
-
responseFormat: outputSchema as any,
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
return { agent };
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
private getModel(params: AICallParams) {
|
|
153
|
-
const { aiModel, modelConfig } = params;
|
|
154
|
-
|
|
155
|
-
const config: LLMModelConfig = {
|
|
156
|
-
model: aiModel,
|
|
157
|
-
maxTokens: modelConfig?.maxTokens,
|
|
158
|
-
temperature: modelConfig?.temperature,
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
if (aiModel.startsWith("gpt")) {
|
|
162
|
-
config.apiKey = this.tokens.openAIApiKey;
|
|
163
|
-
|
|
164
|
-
return AIModels.gpt(config);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (aiModel.startsWith("gemini")) {
|
|
168
|
-
config.apiKey = this.tokens.googleGeminiToken;
|
|
169
|
-
|
|
170
|
-
return AIModels.gemini(config);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (aiModel.startsWith("openrouter/")) {
|
|
174
|
-
const modelName = aiModel.replace(/^openrouter\//, "");
|
|
175
|
-
return AIModels.openrouter({
|
|
176
|
-
...config,
|
|
177
|
-
model: modelName,
|
|
178
|
-
apiKey: this.tokens.openRouterApiKey,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
throw new Error("Model not supported");
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
private standardAgent(
|
|
186
|
-
params: AICallParams
|
|
187
|
-
): Parameters<typeof createAgent>[0] {
|
|
188
|
-
const { systemPrompt, maxRetries = 3 } = params;
|
|
189
|
-
|
|
190
|
-
const model = this.getModel(params);
|
|
191
|
-
return {
|
|
192
|
-
model,
|
|
193
|
-
systemPrompt: systemPrompt ?? "",
|
|
194
|
-
middleware: [
|
|
195
|
-
...this.standardMiddlewares(maxRetries),
|
|
196
|
-
...(params.agent?.middleware ?? []),
|
|
197
|
-
],
|
|
198
|
-
tools: params.agent?.tools ?? [],
|
|
199
|
-
responseFormat: undefined as any,
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
private standardMiddlewares(maxRetries: number) {
|
|
204
|
-
return [
|
|
205
|
-
modelRetryMiddleware({
|
|
206
|
-
maxRetries,
|
|
207
|
-
backoffFactor: 2.0,
|
|
208
|
-
initialDelayMs: 1000,
|
|
209
|
-
}),
|
|
210
|
-
modelFallbackMiddleware("gemini-2.5-flash", "gpt-4o-mini"),
|
|
211
|
-
];
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export { AIModels, AIMessages, AITools };
|
|
2
|
+
import { AI } from "./ai";
|
|
3
|
+
import type {
|
|
4
|
+
AICallParams,
|
|
5
|
+
AICallReturn,
|
|
6
|
+
AICallStructuredOutputParams,
|
|
7
|
+
AICallStructuredOutputReturn,
|
|
8
|
+
} from "./@types/ai-call";
|
|
9
|
+
|
|
10
|
+
export { AI };
|
|
11
|
+
export type { AIAgent } from "./@types/agent";
|
|
12
|
+
export type {
|
|
13
|
+
AICallParams,
|
|
14
|
+
AICallReturn,
|
|
15
|
+
AICallStructuredOutputParams,
|
|
16
|
+
AICallStructuredOutputReturn,
|
|
17
|
+
} from "./@types/ai-call";
|
|
18
|
+
|
|
19
|
+
export { AIModels } from "./langchain/models";
|
|
20
|
+
export { AIMessages } from "./langchain/messages";
|
|
21
|
+
export { AITools } from "./langchain/tools";
|
|
22
|
+
export { AIMemory } from "./langchain/checkpointers";
|
|
23
|
+
export type {
|
|
24
|
+
MemoryConfig,
|
|
25
|
+
BaseCheckpointSaver,
|
|
26
|
+
MemoryCheckpointerConfig,
|
|
27
|
+
SqliteCheckpointerConfig,
|
|
28
|
+
PostgresCheckpointerConfig,
|
|
29
|
+
RedisCheckpointerConfig,
|
|
30
|
+
MongoDBCheckpointerConfig,
|
|
31
|
+
GraphWithStateHistory,
|
|
32
|
+
MessageRole,
|
|
33
|
+
HistoryMessageItem,
|
|
34
|
+
GetHistoryResult,
|
|
35
|
+
} from "./langchain/checkpointers";
|
|
216
36
|
export { AIAudioTranscription } from "./langchain/audio-transcription";
|
|
217
37
|
export { AudioUtils } from "./utils/audio-utils";
|
|
218
38
|
export type { AudioBuffer, AudioMimeType } from "./@types/audio";
|