@luanpoppe/ai 1.1.2 → 1.1.3
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/ai.d.ts +1 -1
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +3 -3
- package/dist/ai.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/langchain/memory.d.ts +96 -0
- package/dist/langchain/memory.d.ts.map +1 -0
- package/dist/langchain/memory.js +268 -0
- package/dist/langchain/memory.js.map +1 -0
- package/package.json +1 -1
- package/src/ai.ts +306 -306
- package/src/index.ts +2 -2
- package/src/langchain/{checkpointers.ts → memory.ts} +48 -22
- package/tests/e2e/ai.test.ts +52 -0
- package/tests/unit/index.test.ts +3 -3
- package/tests/unit/langchain/checkpointers.test.ts +72 -5
package/src/ai.ts
CHANGED
|
@@ -1,306 +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/
|
|
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
|
-
}
|
|
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/memory";
|
|
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
|
@@ -19,7 +19,7 @@ export type {
|
|
|
19
19
|
export { AIModels } from "./langchain/models";
|
|
20
20
|
export { AIMessages } from "./langchain/messages";
|
|
21
21
|
export { AITools } from "./langchain/tools";
|
|
22
|
-
export { AIMemory } from "./langchain/
|
|
22
|
+
export { AIMemory } from "./langchain/memory";
|
|
23
23
|
export type {
|
|
24
24
|
MemoryConfig,
|
|
25
25
|
BaseCheckpointSaver,
|
|
@@ -32,7 +32,7 @@ export type {
|
|
|
32
32
|
MessageRole,
|
|
33
33
|
HistoryMessageItem,
|
|
34
34
|
GetHistoryResult,
|
|
35
|
-
} from "./langchain/
|
|
35
|
+
} from "./langchain/memory";
|
|
36
36
|
export { AIAudioTranscription } from "./langchain/audio-transcription";
|
|
37
37
|
export { AudioUtils } from "./utils/audio-utils";
|
|
38
38
|
export type { AudioBuffer, AudioMimeType } from "./@types/audio";
|