@drax/ai-back 3.28.0 → 3.31.0
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/agents/ChatbotTaskService.js +143 -0
- package/dist/agents/ChatbotTaskTools.js +756 -0
- package/dist/agents/DraxAgent.js +450 -0
- package/dist/controllers/AgentSessionController.js +18 -0
- package/dist/controllers/DraxAgentController.js +114 -0
- package/dist/factory/services/AgentSessionServiceFactory.js +30 -0
- package/dist/index.js +15 -1
- package/dist/interfaces/IAgentSession.js +1 -0
- package/dist/interfaces/IAgentSessionRepository.js +1 -0
- package/dist/interfaces/IBuilderTool.js +1 -0
- package/dist/interfaces/IDraxAgent.js +1 -0
- package/dist/interfaces/IDraxAgentController.js +1 -0
- package/dist/interfaces/IDraxAgentRoutes.js +1 -0
- package/dist/models/AgentSessionModel.js +31 -0
- package/dist/permissions/AgentPermissions.js +6 -0
- package/dist/permissions/AgentSessionPermissions.js +10 -0
- package/dist/repository/mongo/AgentSessionMongoRepository.js +13 -0
- package/dist/repository/sqlite/AgentSessionSqliteRepository.js +34 -0
- package/dist/routes/AgentSessionRoutes.js +21 -0
- package/dist/routes/ChatbotTaskRoutes.js +8 -0
- package/dist/routes/DraxAgentRoutes.js +9 -0
- package/dist/schemas/AgentSessionSchema.js +25 -0
- package/dist/services/AgentSessionService.js +9 -0
- package/dist/tools/BuilderTool.js +248 -0
- package/dist/tools/ToolBuilder.js +243 -0
- package/package.json +4 -4
- package/src/agents/DraxAgent.ts +574 -0
- package/src/controllers/AgentSessionController.ts +29 -0
- package/src/controllers/DraxAgentController.ts +135 -0
- package/src/factory/services/AgentSessionServiceFactory.ts +41 -0
- package/src/index.ts +58 -1
- package/src/interfaces/IAIProvider.ts +8 -0
- package/src/interfaces/IAgentSession.ts +44 -0
- package/src/interfaces/IAgentSessionRepository.ts +11 -0
- package/src/interfaces/IBuilderTool.ts +51 -0
- package/src/interfaces/IDraxAgent.ts +108 -0
- package/src/interfaces/IDraxAgentController.ts +5 -0
- package/src/interfaces/IDraxAgentRoutes.ts +7 -0
- package/src/models/AgentSessionModel.ts +46 -0
- package/src/permissions/AgentPermissions.ts +10 -0
- package/src/permissions/AgentSessionPermissions.ts +14 -0
- package/src/repository/mongo/AgentSessionMongoRepository.ts +22 -0
- package/src/repository/sqlite/AgentSessionSqliteRepository.ts +42 -0
- package/src/routes/AgentSessionRoutes.ts +38 -0
- package/src/routes/DraxAgentRoutes.ts +12 -0
- package/src/schemas/AgentSessionSchema.ts +30 -0
- package/src/services/AgentSessionService.ts +20 -0
- package/src/tools/BuilderTool.ts +289 -0
- package/test/DraxAgent.test.ts +221 -0
- package/test/ToolBuilder.test.ts +90 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/types/agents/ChatbotTaskService.d.ts +42 -0
- package/types/agents/ChatbotTaskService.d.ts.map +1 -0
- package/types/agents/ChatbotTaskTools.d.ts +54 -0
- package/types/agents/ChatbotTaskTools.d.ts.map +1 -0
- package/types/agents/DraxAgent.d.ts +55 -0
- package/types/agents/DraxAgent.d.ts.map +1 -0
- package/types/controllers/AgentSessionController.d.ts +8 -0
- package/types/controllers/AgentSessionController.d.ts.map +1 -0
- package/types/controllers/DraxAgentController.d.ts +16 -0
- package/types/controllers/DraxAgentController.d.ts.map +1 -0
- package/types/factory/services/AgentSessionServiceFactory.d.ts +8 -0
- package/types/factory/services/AgentSessionServiceFactory.d.ts.map +1 -0
- package/types/index.d.ts +14 -2
- package/types/index.d.ts.map +1 -1
- package/types/interfaces/IAIProvider.d.ts +7 -1
- package/types/interfaces/IAIProvider.d.ts.map +1 -1
- package/types/interfaces/IAgentSession.d.ts +39 -0
- package/types/interfaces/IAgentSession.d.ts.map +1 -0
- package/types/interfaces/IAgentSessionRepository.d.ts +6 -0
- package/types/interfaces/IAgentSessionRepository.d.ts.map +1 -0
- package/types/interfaces/IBuilderTool.d.ts +26 -0
- package/types/interfaces/IBuilderTool.d.ts.map +1 -0
- package/types/interfaces/IDraxAgent.d.ts +74 -0
- package/types/interfaces/IDraxAgent.d.ts.map +1 -0
- package/types/interfaces/IDraxAgentController.d.ts +5 -0
- package/types/interfaces/IDraxAgentController.d.ts.map +1 -0
- package/types/interfaces/IDraxAgentRoutes.d.ts +6 -0
- package/types/interfaces/IDraxAgentRoutes.d.ts.map +1 -0
- package/types/models/AgentSessionModel.d.ts +15 -0
- package/types/models/AgentSessionModel.d.ts.map +1 -0
- package/types/permissions/AgentPermissions.d.ts +6 -0
- package/types/permissions/AgentPermissions.d.ts.map +1 -0
- package/types/permissions/AgentSessionPermissions.d.ts +10 -0
- package/types/permissions/AgentSessionPermissions.d.ts.map +1 -0
- package/types/repository/mongo/AgentSessionMongoRepository.d.ts +9 -0
- package/types/repository/mongo/AgentSessionMongoRepository.d.ts.map +1 -0
- package/types/repository/sqlite/AgentSessionSqliteRepository.d.ts +23 -0
- package/types/repository/sqlite/AgentSessionSqliteRepository.d.ts.map +1 -0
- package/types/routes/AgentSessionRoutes.d.ts +4 -0
- package/types/routes/AgentSessionRoutes.d.ts.map +1 -0
- package/types/routes/ChatbotTaskRoutes.d.ts +4 -0
- package/types/routes/ChatbotTaskRoutes.d.ts.map +1 -0
- package/types/routes/DraxAgentRoutes.d.ts +4 -0
- package/types/routes/DraxAgentRoutes.d.ts.map +1 -0
- package/types/schemas/AgentSessionSchema.d.ts +51 -0
- package/types/schemas/AgentSessionSchema.d.ts.map +1 -0
- package/types/services/AgentSessionService.d.ts +10 -0
- package/types/services/AgentSessionService.d.ts.map +1 -0
- package/types/tools/BuilderTool.d.ts +35 -0
- package/types/tools/BuilderTool.d.ts.map +1 -0
- package/types/tools/ToolBuilder.d.ts +47 -0
- package/types/tools/ToolBuilder.d.ts.map +1 -0
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "3.
|
|
6
|
+
"version": "3.31.0",
|
|
7
7
|
"description": "Ai utils",
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"types": "types/index.d.ts",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"author": "Cristian Incarnato & Drax Team",
|
|
19
19
|
"license": "ISC",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@drax/ai-share": "^3.
|
|
22
|
-
"@drax/crud-back": "^3.
|
|
21
|
+
"@drax/ai-share": "^3.31.0",
|
|
22
|
+
"@drax/crud-back": "^3.29.0",
|
|
23
23
|
"mongoose": "^8.23.0",
|
|
24
24
|
"mongoose-paginate-v2": "^1.8.3"
|
|
25
25
|
},
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"typescript": "^5.9.3",
|
|
45
45
|
"vitest": "^3.0.8"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "90e6f71d45f95f14df6b8334920f9af82a1c99a2"
|
|
48
48
|
}
|
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
import {randomUUID} from "node:crypto";
|
|
2
|
+
import type {IPromptTool, IPromptToolNavigation} from "../interfaces/IAIProvider.js";
|
|
3
|
+
import type {
|
|
4
|
+
DraxAgentConfig,
|
|
5
|
+
DraxAgentMessageInput,
|
|
6
|
+
DraxAgentMessageOutput,
|
|
7
|
+
DraxAgentPromptContext,
|
|
8
|
+
DraxAgentSession,
|
|
9
|
+
DraxAgentSessionInput,
|
|
10
|
+
DraxAgentSystemPrompt,
|
|
11
|
+
DraxAgentToolBuilder,
|
|
12
|
+
DraxAgentToolBuilderSource,
|
|
13
|
+
DraxAgentToolSource,
|
|
14
|
+
} from "../interfaces/IDraxAgent.js";
|
|
15
|
+
import type {IPromptMessage} from "../interfaces/IAIProvider.js";
|
|
16
|
+
import type {IAgentSession, IAgentSessionBase} from "../interfaces/IAgentSession.js";
|
|
17
|
+
import AiProviderFactory from "../factory/AiProviderFactory.js";
|
|
18
|
+
import AgentSessionServiceFactory from "../factory/services/AgentSessionServiceFactory.js";
|
|
19
|
+
import type {AgentSessionService} from "../services/AgentSessionService.js";
|
|
20
|
+
|
|
21
|
+
class DraxAgent {
|
|
22
|
+
private static singleton?: DraxAgent;
|
|
23
|
+
|
|
24
|
+
protected sessions: Map<string, DraxAgentSession> = new Map();
|
|
25
|
+
protected config: DraxAgentConfig = {
|
|
26
|
+
systemPrompt: "Sos un asistente del sistema. Responde de forma clara, breve y util.",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
private constructor() {
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static instance(): DraxAgent {
|
|
33
|
+
if (!DraxAgent.singleton) {
|
|
34
|
+
DraxAgent.singleton = new DraxAgent();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return DraxAgent.singleton;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
configure(config: DraxAgentConfig): this {
|
|
41
|
+
this.config = {
|
|
42
|
+
...this.config,
|
|
43
|
+
...config,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
setProvider(provider: DraxAgentConfig["provider"]): this {
|
|
50
|
+
return this.configure({provider});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
setSystemPrompt(systemPrompt: DraxAgentSystemPrompt): this {
|
|
54
|
+
return this.configure({systemPrompt});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
setToolBuilders(toolBuilders: DraxAgentToolBuilderSource): this {
|
|
58
|
+
return this.configure({toolBuilders});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
setTools(tools: DraxAgentToolSource): this {
|
|
62
|
+
return this.configure({tools});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
clearSessions(): this {
|
|
66
|
+
this.sessions.clear();
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async startSession(input: DraxAgentSessionInput = {}): Promise<DraxAgentSession> {
|
|
71
|
+
if (input.sessionId) {
|
|
72
|
+
const existingSession = await this.resolveSession(input);
|
|
73
|
+
if (existingSession) {
|
|
74
|
+
return existingSession;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return this.createSession(input);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getSession(input: DraxAgentSessionInput): DraxAgentSession | null {
|
|
82
|
+
if (!input.sessionId) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return this.sessions.get(this.getSessionKey(input.userId, input.tenantId, input.sessionId)) ?? null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async sendMessage(input: DraxAgentMessageInput): Promise<DraxAgentMessageOutput> {
|
|
90
|
+
const session = await this.resolveSession(input);
|
|
91
|
+
const context: DraxAgentPromptContext = {session, input};
|
|
92
|
+
const toolBuilders = await this.resolveToolBuilders(context);
|
|
93
|
+
const navigationState: {path: string | null} = {path: null};
|
|
94
|
+
const tools = this.prepareTools([
|
|
95
|
+
...toolBuilders.flatMap(builder => builder.getTools()),
|
|
96
|
+
...await this.resolveTools(context),
|
|
97
|
+
], session.id, navigationState);
|
|
98
|
+
|
|
99
|
+
const systemPrompt = await this.buildSystemPrompt(context, toolBuilders);
|
|
100
|
+
const response = await this.getProvider().prompt({
|
|
101
|
+
systemPrompt,
|
|
102
|
+
userInput: input.message,
|
|
103
|
+
userImages: input.userImages,
|
|
104
|
+
userContent: input.userContent,
|
|
105
|
+
inputFiles: input.inputFiles,
|
|
106
|
+
history: session.messages.slice(-(this.config.historyLimit ?? 20)),
|
|
107
|
+
memory: input.memory,
|
|
108
|
+
memoryHeader: input.memoryHeader,
|
|
109
|
+
knowledgeBase: input.knowledgeBase,
|
|
110
|
+
knowledgeBaseHeader: input.knowledgeBaseHeader,
|
|
111
|
+
tools,
|
|
112
|
+
toolMaxIterations: input.toolMaxIterations ?? this.config.toolMaxIterations,
|
|
113
|
+
model: input.model,
|
|
114
|
+
operationTitle: input.operationTitle ?? this.config.operationTitle ?? "drax-agent-message",
|
|
115
|
+
operationGroup: input.operationGroup ?? this.config.operationGroup ?? "drax-agent",
|
|
116
|
+
ip: input.ip,
|
|
117
|
+
userAgent: input.userAgent,
|
|
118
|
+
tenant: input.tenantId ?? session.tenantId ?? null,
|
|
119
|
+
user: input.userId ?? session.userId ?? null,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const assistantMessage = this.normalizeOutput(response.output);
|
|
123
|
+
const now = new Date();
|
|
124
|
+
session.messages.push({role: "user", content: input.message});
|
|
125
|
+
session.messages.push({role: "assistant", content: assistantMessage});
|
|
126
|
+
session.updatedAt = new Date();
|
|
127
|
+
await this.persistSessionUpdate(session, {
|
|
128
|
+
lastMessageAt: now,
|
|
129
|
+
tokens: response.tokens,
|
|
130
|
+
inputTokens: response.inputTokens,
|
|
131
|
+
outputTokens: response.outputTokens,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
sessionId: session.id,
|
|
136
|
+
message: assistantMessage,
|
|
137
|
+
navigationPath: navigationState.path,
|
|
138
|
+
output: response.output,
|
|
139
|
+
tokens: response.tokens,
|
|
140
|
+
inputTokens: response.inputTokens,
|
|
141
|
+
outputTokens: response.outputTokens,
|
|
142
|
+
time: response.time,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
protected async createSession(input: DraxAgentSessionInput = {}): Promise<DraxAgentSession> {
|
|
147
|
+
const session: DraxAgentSession = {
|
|
148
|
+
id: input.sessionId ?? randomUUID(),
|
|
149
|
+
userId: input.userId ?? null,
|
|
150
|
+
tenantId: input.tenantId ?? null,
|
|
151
|
+
messages: [],
|
|
152
|
+
createdAt: new Date(),
|
|
153
|
+
updatedAt: new Date(),
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
this.sessions.set(this.getSessionKey(session.userId, session.tenantId, session.id), session);
|
|
157
|
+
await this.persistSessionCreate(session);
|
|
158
|
+
return session;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
protected async resolveSession(input: DraxAgentSessionInput): Promise<DraxAgentSession> {
|
|
162
|
+
if (!input.sessionId) {
|
|
163
|
+
return this.startSession(input);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const existingSession = this.getSession(input);
|
|
167
|
+
if (existingSession) {
|
|
168
|
+
return existingSession;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const persistedSession = await this.findPersistedSession(input);
|
|
172
|
+
if (persistedSession) {
|
|
173
|
+
const session = this.hydrateSession(persistedSession, input);
|
|
174
|
+
this.sessions.set(this.getSessionKey(session.userId, session.tenantId, session.id), session);
|
|
175
|
+
return session;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return this.createSession(input);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
protected async persistSessionCreate(session: DraxAgentSession): Promise<void> {
|
|
182
|
+
const sessionService = this.getSessionService();
|
|
183
|
+
if (!sessionService) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const record = await sessionService.create(this.buildSessionPayload(session, {
|
|
188
|
+
messageCount: 0,
|
|
189
|
+
inputTokens: 0,
|
|
190
|
+
outputTokens: 0,
|
|
191
|
+
tokens: 0,
|
|
192
|
+
}));
|
|
193
|
+
|
|
194
|
+
session.recordId = this.stringifyRecordId(record);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
protected async persistSessionUpdate(
|
|
198
|
+
session: DraxAgentSession,
|
|
199
|
+
usage: Pick<IAgentSessionBase, "lastMessageAt" | "tokens" | "inputTokens" | "outputTokens">,
|
|
200
|
+
): Promise<void> {
|
|
201
|
+
const sessionService = this.getSessionService();
|
|
202
|
+
if (!sessionService) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!session.recordId) {
|
|
207
|
+
const record = await this.findPersistedSession({
|
|
208
|
+
sessionId: session.id,
|
|
209
|
+
userId: session.userId,
|
|
210
|
+
tenantId: session.tenantId,
|
|
211
|
+
});
|
|
212
|
+
session.recordId = this.stringifyRecordId(record);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (!session.recordId) {
|
|
216
|
+
await this.persistSessionCreate(session);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!session.recordId) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const storedUsage = await this.resolveStoredUsage(session.recordId);
|
|
224
|
+
|
|
225
|
+
await sessionService.updatePartial(session.recordId, this.buildSessionPayload(session, {
|
|
226
|
+
lastMessageAt: usage.lastMessageAt,
|
|
227
|
+
messageCount: session.messages.length,
|
|
228
|
+
inputTokens: storedUsage.inputTokens + (usage.inputTokens ?? 0),
|
|
229
|
+
outputTokens: storedUsage.outputTokens + (usage.outputTokens ?? 0),
|
|
230
|
+
tokens: storedUsage.tokens + (usage.tokens ?? 0),
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
protected buildSessionPayload(session: DraxAgentSession, overrides: Partial<IAgentSessionBase> = {}): IAgentSessionBase {
|
|
235
|
+
return {
|
|
236
|
+
sessionId: session.id,
|
|
237
|
+
title: this.resolveSessionTitle(session.messages),
|
|
238
|
+
lastMessageAt: session.messages.length > 0 ? session.updatedAt : null,
|
|
239
|
+
messages: session.messages.map(message => ({
|
|
240
|
+
role: message.role,
|
|
241
|
+
content: this.stringifyMessageContent(message.content),
|
|
242
|
+
createdAt: session.updatedAt,
|
|
243
|
+
})),
|
|
244
|
+
messageCount: session.messages.length,
|
|
245
|
+
inputTokens: 0,
|
|
246
|
+
outputTokens: 0,
|
|
247
|
+
tokens: 0,
|
|
248
|
+
tenant: session.tenantId ?? null,
|
|
249
|
+
user: session.userId ?? null,
|
|
250
|
+
...overrides,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
protected async findPersistedSession(input: DraxAgentSessionInput): Promise<IAgentSession | null> {
|
|
255
|
+
if (!input.sessionId) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const sessionService = this.getSessionService();
|
|
260
|
+
if (!sessionService) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const filters = [
|
|
265
|
+
...(input.tenantId ? [{field: "tenant", operator: "eq", value: input.tenantId}] : []),
|
|
266
|
+
...(input.userId ? [{field: "user", operator: "eq", value: input.userId}] : []),
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
const sessions = await sessionService.findBy("sessionId", input.sessionId, 1, filters);
|
|
270
|
+
return sessions?.[0] ?? null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
protected hydrateSession(record: IAgentSession, input: DraxAgentSessionInput): DraxAgentSession {
|
|
274
|
+
const session: DraxAgentSession = {
|
|
275
|
+
id: record.sessionId,
|
|
276
|
+
recordId: this.stringifyRecordId(record),
|
|
277
|
+
userId: input.userId ?? this.stringifyRelationId(record.user) ?? null,
|
|
278
|
+
tenantId: input.tenantId ?? this.stringifyRelationId(record.tenant) ?? null,
|
|
279
|
+
messages: this.normalizeStoredMessages(record.messages ?? []),
|
|
280
|
+
createdAt: record.createdAt ? new Date(record.createdAt) : new Date(),
|
|
281
|
+
updatedAt: record.updatedAt ? new Date(record.updatedAt) : new Date(),
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
return session;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
protected normalizeStoredMessages(messages: IAgentSessionBase["messages"]): IPromptMessage[] {
|
|
288
|
+
return (messages ?? [])
|
|
289
|
+
.filter(message => message.role === "user" || message.role === "assistant" || message.role === "system")
|
|
290
|
+
.map(message => ({
|
|
291
|
+
role: message.role as IPromptMessage["role"],
|
|
292
|
+
content: message.content,
|
|
293
|
+
}));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
protected async resolveStoredUsage(recordId: string): Promise<Required<Pick<IAgentSessionBase, "inputTokens" | "outputTokens" | "tokens">>> {
|
|
297
|
+
const sessionService = this.getSessionService();
|
|
298
|
+
if (!sessionService) {
|
|
299
|
+
return {inputTokens: 0, outputTokens: 0, tokens: 0};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const record = await sessionService.findById(recordId);
|
|
303
|
+
return {
|
|
304
|
+
inputTokens: this.normalizeNumber(record?.inputTokens),
|
|
305
|
+
outputTokens: this.normalizeNumber(record?.outputTokens),
|
|
306
|
+
tokens: this.normalizeNumber(record?.tokens),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
protected normalizeNumber(value: any): number {
|
|
311
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
protected resolveSessionTitle(messages: IPromptMessage[]): string | undefined {
|
|
315
|
+
const firstUserMessage = messages.find(message => message.role === "user");
|
|
316
|
+
if (!firstUserMessage) {
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const title = this.stringifyMessageContent(firstUserMessage.content).trim();
|
|
321
|
+
return title.length > 80 ? `${title.slice(0, 77)}...` : title;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
protected stringifyMessageContent(content: IPromptMessage["content"]): string {
|
|
325
|
+
return typeof content === "string" ? content : JSON.stringify(content);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
protected stringifyRecordId(record: IAgentSession | null | undefined): string | null {
|
|
329
|
+
return this.stringifyNavigationId(record?._id ?? (record as any)?.id);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
protected stringifyRelationId(value: any): string | null {
|
|
333
|
+
return this.stringifyNavigationId(value?._id ?? value?.id ?? value);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
protected getSessionService(): AgentSessionService | null {
|
|
337
|
+
if (this.config.sessionService === false) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return this.config.sessionService ?? AgentSessionServiceFactory.instance;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
protected getSessionKey(userId: string | null | undefined, tenantId: string | null | undefined, sessionId: string): string {
|
|
345
|
+
return `${tenantId ?? "global"}:${userId ?? "anonymous"}:${sessionId}`;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
protected async buildSystemPrompt(context: DraxAgentPromptContext, toolBuilders: DraxAgentToolBuilder[]): Promise<string> {
|
|
349
|
+
const systemPrompt = this.config.systemPrompt ?? "";
|
|
350
|
+
const basePrompt = typeof systemPrompt === "function"
|
|
351
|
+
? await systemPrompt(context)
|
|
352
|
+
: systemPrompt;
|
|
353
|
+
const rbacSection = this.buildRbacContextSection(context);
|
|
354
|
+
|
|
355
|
+
const toolSections = toolBuilders
|
|
356
|
+
.map(builder => builder.getSystemPromptSection())
|
|
357
|
+
.filter(section => section.trim().length > 0);
|
|
358
|
+
|
|
359
|
+
return [
|
|
360
|
+
basePrompt,
|
|
361
|
+
rbacSection,
|
|
362
|
+
toolSections.length > 0
|
|
363
|
+
? [
|
|
364
|
+
"[ENTIDADES Y TOOLS]",
|
|
365
|
+
...toolSections,
|
|
366
|
+
].join("\n")
|
|
367
|
+
: "",
|
|
368
|
+
].filter(section => section.trim().length > 0).join("\n\n");
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
protected buildRbacContextSection(context: DraxAgentPromptContext): string {
|
|
372
|
+
const userId = context.input?.userId ?? context.session.userId ?? null;
|
|
373
|
+
const tenantId = context.input?.tenantId ?? context.session.tenantId ?? null;
|
|
374
|
+
|
|
375
|
+
if (!userId && !tenantId) {
|
|
376
|
+
return "";
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const lines = [
|
|
380
|
+
"[CONTEXTO RBAC]",
|
|
381
|
+
`tenantId: ${tenantId ?? "null"}`,
|
|
382
|
+
`userId: ${userId ?? "null"}`,
|
|
383
|
+
"Cuando necesites completar campos de pertenencia o auditoria como tenant, tenantId, user, userId o createdBy, usa estos valores del usuario autenticado. No los solicites al usuario ni inventes otros valores.",
|
|
384
|
+
];
|
|
385
|
+
|
|
386
|
+
return lines.join("\n");
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
protected async resolveToolBuilders(context: DraxAgentPromptContext): Promise<DraxAgentToolBuilder[]> {
|
|
390
|
+
if (!this.config.toolBuilders) {
|
|
391
|
+
return [];
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return typeof this.config.toolBuilders === "function"
|
|
395
|
+
? await this.config.toolBuilders(context)
|
|
396
|
+
: this.config.toolBuilders;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
protected async resolveTools(context: DraxAgentPromptContext): Promise<IPromptTool[]> {
|
|
400
|
+
if (!this.config.tools) {
|
|
401
|
+
return [];
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return typeof this.config.tools === "function"
|
|
405
|
+
? await this.config.tools(context)
|
|
406
|
+
: this.config.tools;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
protected prepareTools(
|
|
410
|
+
tools: IPromptTool[],
|
|
411
|
+
sessionId: string,
|
|
412
|
+
navigationState: {path: string | null},
|
|
413
|
+
): IPromptTool[] {
|
|
414
|
+
return tools.map(tool => ({
|
|
415
|
+
...tool,
|
|
416
|
+
execute: async (args: any) => {
|
|
417
|
+
if (this.config.logToolExecution) {
|
|
418
|
+
console.log("[drax-agent] tool:start", {
|
|
419
|
+
sessionId,
|
|
420
|
+
tool: tool.name,
|
|
421
|
+
args,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
try {
|
|
426
|
+
const result = await tool.execute(args);
|
|
427
|
+
navigationState.path = this.resolveNavigationPath(tool.navigation, args, result) ?? navigationState.path;
|
|
428
|
+
|
|
429
|
+
if (this.config.logToolExecution) {
|
|
430
|
+
console.log("[drax-agent] tool:success", {
|
|
431
|
+
sessionId,
|
|
432
|
+
tool: tool.name,
|
|
433
|
+
navigationPath: navigationState.path,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return result;
|
|
438
|
+
} catch (error) {
|
|
439
|
+
if (this.config.logToolExecution) {
|
|
440
|
+
console.error("[drax-agent] tool:error", {
|
|
441
|
+
sessionId,
|
|
442
|
+
tool: tool.name,
|
|
443
|
+
error,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
throw error;
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
}));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
protected resolveNavigationPath(navigation: IPromptToolNavigation | undefined, args: any, result: any): string | null {
|
|
454
|
+
if (!navigation) {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const mode = this.resolveNavigationMode(navigation.method);
|
|
459
|
+
|
|
460
|
+
if (!mode) {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const id = this.resolveNavigationId(navigation.method, args, result);
|
|
465
|
+
|
|
466
|
+
if (!id) {
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const basePath = navigation.basePath ?? `/crud/${encodeURIComponent(navigation.entityName)}`;
|
|
471
|
+
const query = new URLSearchParams({
|
|
472
|
+
mode,
|
|
473
|
+
id,
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
return `${basePath}?${query.toString()}`;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
protected resolveNavigationMode(method: string): "edit" | "view" | null {
|
|
480
|
+
if (["create", "update", "updatePartial"].includes(method)) {
|
|
481
|
+
return "edit";
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (["findById", "findOneBy", "findOne", "findBy", "search", "find", "paginate"].includes(method)) {
|
|
485
|
+
return "view";
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
protected resolveNavigationId(method: string, args: any, result: any): string | null {
|
|
492
|
+
if (["update", "updatePartial", "findById"].includes(method)) {
|
|
493
|
+
return this.stringifyNavigationId(args?.id) ?? this.extractRecordId(result);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return this.extractRecordId(result);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
protected extractRecordId(result: any): string | null {
|
|
500
|
+
const record = this.resolveSingleRecord(result);
|
|
501
|
+
|
|
502
|
+
if (!record || typeof record !== "object") {
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return this.stringifyNavigationId(record.id)
|
|
507
|
+
?? this.stringifyNavigationId(record._id)
|
|
508
|
+
?? this.stringifyNavigationId(record.uuid);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
protected resolveSingleRecord(result: any): any {
|
|
512
|
+
if (Array.isArray(result)) {
|
|
513
|
+
return result.length === 1 ? result[0] : null;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (Array.isArray(result?.docs)) {
|
|
517
|
+
return result.docs.length === 1 ? result.docs[0] : null;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (Array.isArray(result?.items)) {
|
|
521
|
+
return result.items.length === 1 ? result.items[0] : null;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (Array.isArray(result?.data)) {
|
|
525
|
+
return result.data.length === 1 ? result.data[0] : null;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return result;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
protected stringifyNavigationId(id: any): string | null {
|
|
532
|
+
if (id === null || id === undefined || id === "") {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (typeof id === "string" || typeof id === "number" || typeof id === "boolean") {
|
|
537
|
+
return String(id);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (typeof id?.toString === "function") {
|
|
541
|
+
const value = id.toString();
|
|
542
|
+
return value && value !== "[object Object]" ? value : null;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
protected normalizeOutput(output: any): string {
|
|
549
|
+
if (this.config.normalizeOutput) {
|
|
550
|
+
return this.config.normalizeOutput(output);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (typeof output === "string") {
|
|
554
|
+
return output;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (output?.message && typeof output.message === "string") {
|
|
558
|
+
return output.message;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return JSON.stringify(output);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
protected getProvider() {
|
|
565
|
+
if (!this.config.provider) {
|
|
566
|
+
this.config.provider = AiProviderFactory.instance()
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
return this.config.provider;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export default DraxAgent;
|
|
574
|
+
export {DraxAgent};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
|
|
2
|
+
import AgentSessionServiceFactory from "../factory/services/AgentSessionServiceFactory.js";
|
|
3
|
+
import {AbstractFastifyController} from "@drax/crud-back";
|
|
4
|
+
import AgentSessionPermissions from "../permissions/AgentSessionPermissions.js";
|
|
5
|
+
import type {IAgentSession, IAgentSessionBase} from "../interfaces/IAgentSession";
|
|
6
|
+
|
|
7
|
+
class AgentSessionController extends AbstractFastifyController<IAgentSession, IAgentSessionBase, IAgentSessionBase> {
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
super(AgentSessionServiceFactory.instance, AgentSessionPermissions)
|
|
11
|
+
this.tenantField = "tenant";
|
|
12
|
+
this.userField = "user";
|
|
13
|
+
|
|
14
|
+
this.tenantFilter = true;
|
|
15
|
+
this.tenantSetter = true;
|
|
16
|
+
this.tenantAssert = true;
|
|
17
|
+
|
|
18
|
+
this.userFilter = true;
|
|
19
|
+
this.userSetter = true;
|
|
20
|
+
this.userAssert = true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default AgentSessionController;
|
|
26
|
+
export {
|
|
27
|
+
AgentSessionController
|
|
28
|
+
}
|
|
29
|
+
|