@drax/ai-back 3.51.1 → 3.52.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.
Files changed (34) hide show
  1. package/dist/models/AILogModel.js +1 -0
  2. package/dist/providers/ai/AbstractAiProvider.js +90 -0
  3. package/dist/providers/ai/DeepSeekAiProvider.js +1 -7
  4. package/dist/providers/ai/GoogleAiProvider.js +3 -80
  5. package/dist/providers/ai/OllamaAiProvider.js +3 -80
  6. package/dist/providers/ai/OpenAiProvider.js +4 -81
  7. package/dist/repository/sqlite/AILogSqliteRepository.js +1 -0
  8. package/dist/routes/AILogRoutes.js +5 -5
  9. package/dist/schemas/AILogSchema.js +4 -1
  10. package/package.json +3 -3
  11. package/src/models/AILogModel.ts +1 -0
  12. package/src/providers/ai/AbstractAiProvider.ts +129 -0
  13. package/src/providers/ai/DeepSeekAiProvider.ts +1 -20
  14. package/src/providers/ai/GoogleAiProvider.ts +4 -116
  15. package/src/providers/ai/OllamaAiProvider.ts +4 -116
  16. package/src/providers/ai/OpenAiProvider.ts +5 -117
  17. package/src/repository/sqlite/AILogSqliteRepository.ts +1 -1
  18. package/src/routes/AILogRoutes.ts +14 -14
  19. package/src/schemas/AILogSchema.ts +4 -1
  20. package/tsconfig.tsbuildinfo +1 -1
  21. package/types/models/AILogModel.d.ts.map +1 -1
  22. package/types/providers/ai/AbstractAiProvider.d.ts +29 -0
  23. package/types/providers/ai/AbstractAiProvider.d.ts.map +1 -0
  24. package/types/providers/ai/DeepSeekAiProvider.d.ts +0 -14
  25. package/types/providers/ai/DeepSeekAiProvider.d.ts.map +1 -1
  26. package/types/providers/ai/GoogleAiProvider.d.ts +3 -31
  27. package/types/providers/ai/GoogleAiProvider.d.ts.map +1 -1
  28. package/types/providers/ai/OllamaAiProvider.d.ts +3 -31
  29. package/types/providers/ai/OllamaAiProvider.d.ts.map +1 -1
  30. package/types/providers/ai/OpenAiProvider.d.ts +4 -32
  31. package/types/providers/ai/OpenAiProvider.d.ts.map +1 -1
  32. package/types/repository/sqlite/AILogSqliteRepository.d.ts.map +1 -1
  33. package/types/schemas/AILogSchema.d.ts +4 -0
  34. package/types/schemas/AILogSchema.d.ts.map +1 -1
@@ -29,6 +29,7 @@ const AILogSchema = new mongoose.Schema({
29
29
  startedAt: { type: Date, required: false, index: false, unique: false },
30
30
  endedAt: { type: Date, required: false, index: false, unique: false },
31
31
  responseTime: { type: String, required: false, index: false, unique: false },
32
+ responseTimeMS: { type: Number, required: false, index: false, unique: false },
32
33
  output: { type: String, required: false, index: false, unique: false },
33
34
  success: { type: Boolean, required: false, index: false, unique: false },
34
35
  statusCode: { type: Number, required: false, index: false, unique: false },
@@ -0,0 +1,90 @@
1
+ class AbstractAiProvider {
2
+ constructor(providerName, aiLogService) {
3
+ this.providerName = providerName;
4
+ this._aiLogService = aiLogService;
5
+ }
6
+ hasImageInput(input) {
7
+ if (input.userImages && input.userImages.length > 0) {
8
+ return true;
9
+ }
10
+ if (input.userContent?.some(part => part.type === 'image')) {
11
+ return true;
12
+ }
13
+ return input.history?.some(message => Array.isArray(message.content) && message.content.some(part => part.type === 'image')) ?? false;
14
+ }
15
+ serializePromptInput(input, systemPrompt) {
16
+ return JSON.stringify({
17
+ systemPrompt,
18
+ history: input.history,
19
+ userInput: input.userInput,
20
+ userContent: input.userContent,
21
+ memory: input.memory,
22
+ knowledgeBase: input.knowledgeBase,
23
+ tools: input.tools?.map(tool => ({
24
+ name: tool.name,
25
+ description: tool.description,
26
+ parameters: tool.parameters,
27
+ })),
28
+ });
29
+ }
30
+ serializePromptOutput(output) {
31
+ if (typeof output === "string") {
32
+ return output;
33
+ }
34
+ if (output === null || output === undefined) {
35
+ return undefined;
36
+ }
37
+ return JSON.stringify(output);
38
+ }
39
+ buildLogPayload(input, params) {
40
+ const responseTimeMS = params.endedAt
41
+ ? params.endedAt.getTime() - params.startedAt.getTime()
42
+ : undefined;
43
+ return {
44
+ provider: this.providerName,
45
+ model: params.model,
46
+ operationTitle: input.operationTitle,
47
+ operationGroup: input.operationGroup,
48
+ ip: input.ip,
49
+ userAgent: input.userAgent,
50
+ input: this.serializePromptInput(input, params.systemPrompt),
51
+ inputImages: input.userImages?.map(image => ({
52
+ url: image.url,
53
+ })) ?? input.userContent
54
+ ?.filter(part => part.type === "image")
55
+ .map(part => ({
56
+ url: part.imageUrl,
57
+ })),
58
+ inputFiles: input.inputFiles,
59
+ inputTokens: params.inputTokens,
60
+ outputTokens: params.outputTokens,
61
+ tokens: params.tokens,
62
+ startedAt: params.startedAt,
63
+ endedAt: params.endedAt,
64
+ responseTime: responseTimeMS !== undefined ? `${responseTimeMS}ms` : undefined,
65
+ responseTimeMS,
66
+ output: this.serializePromptOutput(params.output),
67
+ success: params.success,
68
+ errorMessage: params.errorMessage,
69
+ tenant: input.tenant,
70
+ user: input.user,
71
+ };
72
+ }
73
+ async registerPromptLog(input, params) {
74
+ if (!this._aiLogService) {
75
+ return;
76
+ }
77
+ try {
78
+ await this._aiLogService.create(this.buildLogPayload(input, params));
79
+ }
80
+ catch (e) {
81
+ console.error("Error registerPromptLog", {
82
+ name: e?.name,
83
+ message: e?.message,
84
+ stack: e?.stack,
85
+ });
86
+ }
87
+ }
88
+ }
89
+ export default AbstractAiProvider;
90
+ export { AbstractAiProvider };
@@ -8,7 +8,7 @@ class DeepSeekAiProvider extends OpenAiProvider {
8
8
  if (!model) {
9
9
  throw new Error("DeepSeek model required");
10
10
  }
11
- super(apiKey, model, visionModel, aiLogService);
11
+ super(apiKey, model, visionModel, aiLogService, "deepseek");
12
12
  if (!baseUrl) {
13
13
  throw new Error("DeepSeek baseUrl required");
14
14
  }
@@ -23,12 +23,6 @@ class DeepSeekAiProvider extends OpenAiProvider {
23
23
  }
24
24
  return this._client;
25
25
  }
26
- buildLogPayload(input, params) {
27
- return {
28
- ...super.buildLogPayload(input, params),
29
- provider: "deepseek",
30
- };
31
- }
32
26
  }
33
27
  export default DeepSeekAiProvider;
34
28
  export { DeepSeekAiProvider };
@@ -1,7 +1,8 @@
1
1
  import { GoogleGenAI } from "@google/genai";
2
2
  import { toJSONSchema } from "zod";
3
3
  import PromptAudioService from "../../services/PromptAudioService.js";
4
- class GoogleAiProvider {
4
+ import AbstractAiProvider from "./AbstractAiProvider.js";
5
+ class GoogleAiProvider extends AbstractAiProvider {
5
6
  constructor(apiKey, model, visionModel, aiLogService) {
6
7
  if (!apiKey) {
7
8
  throw new Error("Google AI apiKey required");
@@ -9,10 +10,10 @@ class GoogleAiProvider {
9
10
  if (!model) {
10
11
  throw new Error("Google AI model required");
11
12
  }
13
+ super("googleai", aiLogService);
12
14
  this._apiKey = apiKey;
13
15
  this._model = model;
14
16
  this._visionModel = visionModel;
15
- this._aiLogService = aiLogService;
16
17
  }
17
18
  get model() {
18
19
  if (!this._model) {
@@ -120,84 +121,6 @@ class GoogleAiProvider {
120
121
  };
121
122
  });
122
123
  }
123
- hasImageInput(input) {
124
- if (input.userImages && input.userImages.length > 0) {
125
- return true;
126
- }
127
- if (input.userContent?.some(part => part.type === 'image')) {
128
- return true;
129
- }
130
- return input.history?.some(message => Array.isArray(message.content) && message.content.some(part => part.type === 'image')) ?? false;
131
- }
132
- serializePromptInput(input, systemPrompt) {
133
- return JSON.stringify({
134
- systemPrompt,
135
- history: input.history,
136
- userInput: input.userInput,
137
- userContent: input.userContent,
138
- memory: input.memory,
139
- knowledgeBase: input.knowledgeBase,
140
- tools: input.tools?.map(tool => ({
141
- name: tool.name,
142
- description: tool.description,
143
- parameters: tool.parameters,
144
- })),
145
- });
146
- }
147
- serializePromptOutput(output) {
148
- if (typeof output === "string") {
149
- return output;
150
- }
151
- if (output === null || output === undefined) {
152
- return undefined;
153
- }
154
- return JSON.stringify(output);
155
- }
156
- buildLogPayload(input, params) {
157
- return {
158
- provider: "googleai",
159
- model: params.model,
160
- operationTitle: input.operationTitle,
161
- operationGroup: input.operationGroup,
162
- ip: input.ip,
163
- userAgent: input.userAgent,
164
- input: this.serializePromptInput(input, params.systemPrompt),
165
- inputImages: input.userImages?.map(image => ({
166
- url: image.url,
167
- })) ?? input.userContent
168
- ?.filter(part => part.type === "image")
169
- .map(part => ({
170
- url: part.imageUrl,
171
- })),
172
- inputFiles: input.inputFiles,
173
- inputTokens: params.inputTokens,
174
- outputTokens: params.outputTokens,
175
- tokens: params.tokens,
176
- startedAt: params.startedAt,
177
- endedAt: params.endedAt,
178
- responseTime: params.endedAt ? `${params.endedAt.getTime() - params.startedAt.getTime()}ms` : undefined,
179
- output: this.serializePromptOutput(params.output),
180
- success: params.success,
181
- errorMessage: params.errorMessage,
182
- tenant: input.tenant,
183
- user: input.user,
184
- };
185
- }
186
- async registerPromptLog(input, params) {
187
- if (!this._aiLogService) {
188
- return;
189
- }
190
- try {
191
- await this._aiLogService.create(this.buildLogPayload(input, params));
192
- }
193
- catch (e) {
194
- console.error("Error registerPromptLog", {
195
- name: e?.name,
196
- message: e?.message,
197
- stack: e?.stack,
198
- });
199
- }
200
- }
201
124
  async generateEmbedding({ text, model = "text-embedding-004" }) {
202
125
  const response = await this.client.models.embedContent({
203
126
  model,
@@ -1,6 +1,7 @@
1
1
  import { toJSONSchema } from "zod";
2
2
  import PromptAudioService from "../../services/PromptAudioService.js";
3
- class OllamaAiProvider {
3
+ import AbstractAiProvider from "./AbstractAiProvider.js";
4
+ class OllamaAiProvider extends AbstractAiProvider {
4
5
  constructor(baseUrl, model, visionModel, embeddingModel, aiLogService) {
5
6
  if (!baseUrl) {
6
7
  throw new Error("Ollama AI baseUrl required");
@@ -8,11 +9,11 @@ class OllamaAiProvider {
8
9
  if (!model) {
9
10
  throw new Error("Ollama AI model required");
10
11
  }
12
+ super("ollamaai", aiLogService);
11
13
  this._baseUrl = baseUrl.replace(/\/+$/, "");
12
14
  this._model = model;
13
15
  this._visionModel = visionModel;
14
16
  this._embeddingModel = embeddingModel;
15
- this._aiLogService = aiLogService;
16
17
  }
17
18
  get model() {
18
19
  if (!this._model) {
@@ -98,84 +99,6 @@ class OllamaAiProvider {
98
99
  }
99
100
  return messages;
100
101
  }
101
- hasImageInput(input) {
102
- if (input.userImages && input.userImages.length > 0) {
103
- return true;
104
- }
105
- if (input.userContent?.some(part => part.type === 'image')) {
106
- return true;
107
- }
108
- return input.history?.some(message => Array.isArray(message.content) && message.content.some(part => part.type === 'image')) ?? false;
109
- }
110
- serializePromptInput(input, systemPrompt) {
111
- return JSON.stringify({
112
- systemPrompt,
113
- history: input.history,
114
- userInput: input.userInput,
115
- userContent: input.userContent,
116
- memory: input.memory,
117
- knowledgeBase: input.knowledgeBase,
118
- tools: input.tools?.map(tool => ({
119
- name: tool.name,
120
- description: tool.description,
121
- parameters: tool.parameters,
122
- })),
123
- });
124
- }
125
- serializePromptOutput(output) {
126
- if (typeof output === "string") {
127
- return output;
128
- }
129
- if (output === null || output === undefined) {
130
- return undefined;
131
- }
132
- return JSON.stringify(output);
133
- }
134
- buildLogPayload(input, params) {
135
- return {
136
- provider: "ollamaai",
137
- model: params.model,
138
- operationTitle: input.operationTitle,
139
- operationGroup: input.operationGroup,
140
- ip: input.ip,
141
- userAgent: input.userAgent,
142
- input: this.serializePromptInput(input, params.systemPrompt),
143
- inputImages: input.userImages?.map(image => ({
144
- url: image.url,
145
- })) ?? input.userContent
146
- ?.filter(part => part.type === "image")
147
- .map(part => ({
148
- url: part.imageUrl,
149
- })),
150
- inputFiles: input.inputFiles,
151
- inputTokens: params.inputTokens,
152
- outputTokens: params.outputTokens,
153
- tokens: params.tokens,
154
- startedAt: params.startedAt,
155
- endedAt: params.endedAt,
156
- responseTime: params.endedAt ? `${params.endedAt.getTime() - params.startedAt.getTime()}ms` : undefined,
157
- output: this.serializePromptOutput(params.output),
158
- success: params.success,
159
- errorMessage: params.errorMessage,
160
- tenant: input.tenant,
161
- user: input.user,
162
- };
163
- }
164
- async registerPromptLog(input, params) {
165
- if (!this._aiLogService) {
166
- return;
167
- }
168
- try {
169
- await this._aiLogService.create(this.buildLogPayload(input, params));
170
- }
171
- catch (e) {
172
- console.error("Error registerPromptLog", {
173
- name: e?.name,
174
- message: e?.message,
175
- stack: e?.stack,
176
- });
177
- }
178
- }
179
102
  async generateEmbedding({ text, model }) {
180
103
  const response = await this.post("/api/embed", {
181
104
  model: model ?? this.embeddingModel,
@@ -1,18 +1,19 @@
1
1
  import OpenAI from "openai";
2
2
  import { zodResponseFormat } from "openai/helpers/zod";
3
3
  import PromptAudioService from "../../services/PromptAudioService.js";
4
- class OpenAiProvider {
5
- constructor(apiKey, model, visionModel, aiLogService) {
4
+ import AbstractAiProvider from "./AbstractAiProvider.js";
5
+ class OpenAiProvider extends AbstractAiProvider {
6
+ constructor(apiKey, model, visionModel, aiLogService, providerName = "openai") {
6
7
  if (!apiKey) {
7
8
  throw new Error("OpenAI apiKey required");
8
9
  }
9
10
  if (!model) {
10
11
  throw new Error("OpenAI model required");
11
12
  }
13
+ super(providerName, aiLogService);
12
14
  this._apiKey = apiKey;
13
15
  this._model = model;
14
16
  this._visionModel = visionModel;
15
- this._aiLogService = aiLogService;
16
17
  }
17
18
  get model() {
18
19
  if (!this._model) {
@@ -76,84 +77,6 @@ class OpenAiProvider {
76
77
  : this.mapContentParts(message.content)
77
78
  }));
78
79
  }
79
- hasImageInput(input) {
80
- if (input.userImages && input.userImages.length > 0) {
81
- return true;
82
- }
83
- if (input.userContent?.some(part => part.type === 'image')) {
84
- return true;
85
- }
86
- return input.history?.some(message => Array.isArray(message.content) && message.content.some(part => part.type === 'image')) ?? false;
87
- }
88
- serializePromptInput(input, systemPrompt) {
89
- return JSON.stringify({
90
- systemPrompt,
91
- history: input.history,
92
- userInput: input.userInput,
93
- userContent: input.userContent,
94
- memory: input.memory,
95
- knowledgeBase: input.knowledgeBase,
96
- tools: input.tools?.map(tool => ({
97
- name: tool.name,
98
- description: tool.description,
99
- parameters: tool.parameters,
100
- })),
101
- });
102
- }
103
- serializePromptOutput(output) {
104
- if (typeof output === "string") {
105
- return output;
106
- }
107
- if (output === null || output === undefined) {
108
- return undefined;
109
- }
110
- return JSON.stringify(output);
111
- }
112
- buildLogPayload(input, params) {
113
- return {
114
- provider: "openai",
115
- model: params.model,
116
- operationTitle: input.operationTitle,
117
- operationGroup: input.operationGroup,
118
- ip: input.ip,
119
- userAgent: input.userAgent,
120
- input: this.serializePromptInput(input, params.systemPrompt),
121
- inputImages: input.userImages?.map(image => ({
122
- url: image.url,
123
- })) ?? input.userContent
124
- ?.filter(part => part.type === "image")
125
- .map(part => ({
126
- url: part.imageUrl,
127
- })),
128
- inputFiles: input.inputFiles,
129
- inputTokens: params.inputTokens,
130
- outputTokens: params.outputTokens,
131
- tokens: params.tokens,
132
- startedAt: params.startedAt,
133
- endedAt: params.endedAt,
134
- responseTime: params.endedAt ? `${params.endedAt.getTime() - params.startedAt.getTime()}ms` : undefined,
135
- output: this.serializePromptOutput(params.output),
136
- success: params.success,
137
- errorMessage: params.errorMessage,
138
- tenant: input.tenant,
139
- user: input.user,
140
- };
141
- }
142
- async registerPromptLog(input, params) {
143
- if (!this._aiLogService) {
144
- return;
145
- }
146
- try {
147
- await this._aiLogService.create(this.buildLogPayload(input, params));
148
- }
149
- catch (e) {
150
- console.error("Error registerPromptLog", {
151
- name: e?.name,
152
- message: e?.message,
153
- stack: e?.stack,
154
- });
155
- }
156
- }
157
80
  async generateEmbedding({ text, model = "text-embedding-ada-002" }) {
158
81
  const response = await this.client.embeddings.create({
159
82
  model: model,
@@ -31,6 +31,7 @@ class AILogSqliteRepository extends AbstractSqliteRepository {
31
31
  { name: "startedAt", type: "TEXT", unique: false, primary: false },
32
32
  { name: "endedAt", type: "TEXT", unique: false, primary: false },
33
33
  { name: "responseTime", type: "TEXT", unique: false, primary: false },
34
+ { name: "responseTimeMS", type: "REAL", unique: false, primary: false },
34
35
  { name: "output", type: "TEXT", unique: false, primary: false },
35
36
  { name: "success", type: "TEXT", unique: false, primary: false },
36
37
  { name: "statusCode", type: "INTEGER", unique: false, primary: false },
@@ -10,12 +10,12 @@ async function AILogFastifyRoutes(fastify, options) {
10
10
  fastify.get('/api/ailog/:id', { schema: schemas.findByIdSchema }, (req, rep) => controller.findById(req, rep));
11
11
  fastify.get('/api/ailog/find-one', { schema: schemas.findOneSchema }, (req, rep) => controller.findOne(req, rep));
12
12
  fastify.get('/api/ailog/group-by', { schema: schemas.groupBySchema }, (req, rep) => controller.groupBy(req, rep));
13
- fastify.post('/api/ailog', { schema: schemas.createSchema }, (req, rep) => controller.create(req, rep));
14
- fastify.put('/api/ailog/:id', { schema: schemas.updateSchema }, (req, rep) => controller.update(req, rep));
15
- fastify.patch('/api/ailog/:id', { schema: schemas.updateSchema }, (req, rep) => controller.updatePartial(req, rep));
16
- fastify.delete('/api/ailog/:id', { schema: schemas.deleteSchema }, (req, rep) => controller.delete(req, rep));
13
+ // fastify.post('/api/ailog', {schema: schemas.createSchema}, (req,rep) =>controller.create(req,rep))
14
+ // fastify.put('/api/ailog/:id', {schema: schemas.updateSchema}, (req,rep) =>controller.update(req,rep))
15
+ // fastify.patch('/api/ailog/:id', {schema: schemas.updateSchema}, (req,rep) =>controller.updatePartial(req,rep))
16
+ // fastify.delete('/api/ailog/:id', {schema: schemas.deleteSchema}, (req,rep) =>controller.delete(req,rep))
17
17
  fastify.get('/api/ailog/export', (req, rep) => controller.export(req, rep));
18
- fastify.post('/api/ailog/import', (req, rep) => controller.import(req, rep));
18
+ // fastify.post('/api/ailog/import', (req,rep) => controller.import(req,rep))
19
19
  }
20
20
  export default AILogFastifyRoutes;
21
21
  export { AILogFastifyRoutes };
@@ -27,6 +27,7 @@ const AILogBaseSchema = z.object({
27
27
  startedAt: z.coerce.date().nullable().optional(),
28
28
  endedAt: z.coerce.date().nullable().optional(),
29
29
  responseTime: z.string().optional(),
30
+ responseTimeMS: z.number().nullable().optional(),
30
31
  output: z.string().optional(),
31
32
  success: z.boolean().optional(),
32
33
  statusCode: z.number().nullable().optional(),
@@ -38,7 +39,9 @@ const AILogSchema = AILogBaseSchema
38
39
  .extend({
39
40
  _id: z.coerce.string(),
40
41
  tenant: z.object({ _id: z.coerce.string(), name: z.string() }).nullable().optional(),
41
- user: z.object({ _id: z.coerce.string(), username: z.string() }).nullable().optional()
42
+ user: z.object({ _id: z.coerce.string(), username: z.string() }).nullable().optional(),
43
+ createdAt: z.coerce.date().nullable().optional(),
44
+ updatedAt: z.coerce.date().nullable().optional(),
42
45
  });
43
46
  export default AILogSchema;
44
47
  export { AILogSchema, AILogBaseSchema };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "3.51.1",
6
+ "version": "3.52.0",
7
7
  "description": "Ai utils",
8
8
  "main": "dist/index.js",
9
9
  "types": "types/index.d.ts",
@@ -18,7 +18,7 @@
18
18
  "author": "Cristian Incarnato & Drax Team",
19
19
  "license": "ISC",
20
20
  "dependencies": {
21
- "@drax/ai-share": "^3.45.0",
21
+ "@drax/ai-share": "^3.52.0",
22
22
  "@drax/crud-back": "^3.51.0",
23
23
  "mongoose": "^8.23.0",
24
24
  "mongoose-paginate-v2": "^1.8.3"
@@ -46,5 +46,5 @@
46
46
  "typescript": "^5.9.3",
47
47
  "vitest": "^3.0.8"
48
48
  },
49
- "gitHead": "f69448d61717e8a83d98d427c8606dc979da030f"
49
+ "gitHead": "3c78a6b0da9e2b69b231c57730c450964d532978"
50
50
  }
@@ -33,6 +33,7 @@ const AILogSchema = new mongoose.Schema<IAILog>({
33
33
  startedAt: {type: Date, required: false, index: false, unique: false },
34
34
  endedAt: {type: Date, required: false, index: false, unique: false },
35
35
  responseTime: {type: String, required: false, index: false, unique: false },
36
+ responseTimeMS: {type: Number, required: false, index: false, unique: false },
36
37
  output: {type: String, required: false, index: false, unique: false },
37
38
  success: {type: Boolean, required: false, index: false, unique: false },
38
39
  statusCode: {type: Number, required: false, index: false, unique: false },
@@ -0,0 +1,129 @@
1
+ import type {IAILogBase} from "@drax/ai-share";
2
+ import type {
3
+ IAIProvider,
4
+ IPromptParams,
5
+ IPromptResponse,
6
+ } from "../../interfaces/IAIProvider.js";
7
+ import type {AILogService} from "../../services/AILogService.js";
8
+
9
+ type PromptLogParams = {
10
+ model: string,
11
+ systemPrompt: string,
12
+ startedAt: Date,
13
+ endedAt?: Date,
14
+ inputTokens?: number,
15
+ outputTokens?: number,
16
+ tokens?: number,
17
+ output?: unknown,
18
+ success: boolean,
19
+ errorMessage?: string,
20
+ }
21
+
22
+ abstract class AbstractAiProvider implements IAIProvider {
23
+ protected readonly providerName: string
24
+ protected _aiLogService?: AILogService
25
+
26
+ protected constructor(providerName: string, aiLogService?: AILogService) {
27
+ this.providerName = providerName
28
+ this._aiLogService = aiLogService
29
+ }
30
+
31
+ abstract prompt(input: IPromptParams): Promise<IPromptResponse>
32
+
33
+ protected hasImageInput(input: IPromptParams){
34
+ if(input.userImages && input.userImages.length > 0){
35
+ return true
36
+ }
37
+
38
+ if(input.userContent?.some(part => part.type === 'image')){
39
+ return true
40
+ }
41
+
42
+ return input.history?.some(message =>
43
+ Array.isArray(message.content) && message.content.some(part => part.type === 'image')
44
+ ) ?? false
45
+ }
46
+
47
+ protected serializePromptInput(input: IPromptParams, systemPrompt: string){
48
+ return JSON.stringify({
49
+ systemPrompt,
50
+ history: input.history,
51
+ userInput: input.userInput,
52
+ userContent: input.userContent,
53
+ memory: input.memory,
54
+ knowledgeBase: input.knowledgeBase,
55
+ tools: input.tools?.map(tool => ({
56
+ name: tool.name,
57
+ description: tool.description,
58
+ parameters: tool.parameters,
59
+ })),
60
+ })
61
+ }
62
+
63
+ protected serializePromptOutput(output: unknown){
64
+ if (typeof output === "string") {
65
+ return output
66
+ }
67
+
68
+ if (output === null || output === undefined) {
69
+ return undefined
70
+ }
71
+
72
+ return JSON.stringify(output)
73
+ }
74
+
75
+ protected buildLogPayload(input: IPromptParams, params: PromptLogParams): IAILogBase {
76
+ const responseTimeMS = params.endedAt
77
+ ? params.endedAt.getTime() - params.startedAt.getTime()
78
+ : undefined
79
+
80
+ return {
81
+ provider: this.providerName,
82
+ model: params.model,
83
+ operationTitle: input.operationTitle,
84
+ operationGroup: input.operationGroup,
85
+ ip: input.ip,
86
+ userAgent: input.userAgent,
87
+ input: this.serializePromptInput(input, params.systemPrompt),
88
+ inputImages: input.userImages?.map(image => ({
89
+ url: image.url,
90
+ })) ?? input.userContent
91
+ ?.filter(part => part.type === "image")
92
+ .map(part => ({
93
+ url: part.imageUrl,
94
+ })),
95
+ inputFiles: input.inputFiles,
96
+ inputTokens: params.inputTokens,
97
+ outputTokens: params.outputTokens,
98
+ tokens: params.tokens,
99
+ startedAt: params.startedAt,
100
+ endedAt: params.endedAt,
101
+ responseTime: responseTimeMS !== undefined ? `${responseTimeMS}ms` : undefined,
102
+ responseTimeMS,
103
+ output: this.serializePromptOutput(params.output),
104
+ success: params.success,
105
+ errorMessage: params.errorMessage,
106
+ tenant: input.tenant,
107
+ user: input.user,
108
+ }
109
+ }
110
+
111
+ protected async registerPromptLog(input: IPromptParams, params: PromptLogParams){
112
+ if(!this._aiLogService){
113
+ return
114
+ }
115
+
116
+ try{
117
+ await this._aiLogService.create(this.buildLogPayload(input, params))
118
+ }catch(e: any){
119
+ console.error("Error registerPromptLog", {
120
+ name: e?.name,
121
+ message: e?.message,
122
+ stack: e?.stack,
123
+ })
124
+ }
125
+ }
126
+ }
127
+
128
+ export default AbstractAiProvider
129
+ export {AbstractAiProvider}