@drax/ai-back 3.54.0 → 3.55.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/config/AiConfig.js +6 -0
- package/dist/factory/ai/AiProviderFactory.js +6 -1
- package/dist/index.js +2 -1
- package/dist/providers/ai/AbstractAiProvider.js +53 -0
- package/dist/providers/ai/DeepSeekAiProvider.js +209 -3
- package/dist/providers/ai/GoogleAiProvider.js +4 -20
- package/dist/providers/ai/OllamaAiProvider.js +4 -43
- package/dist/providers/ai/OpenAiProvider.js +4 -40
- package/package.json +3 -3
- package/src/config/AiConfig.ts +10 -0
- package/src/factory/ai/AiProviderFactory.ts +6 -1
- package/src/index.ts +2 -0
- package/src/providers/ai/AbstractAiProvider.ts +69 -0
- package/src/providers/ai/DeepSeekAiProvider.ts +263 -5
- package/src/providers/ai/GoogleAiProvider.ts +4 -26
- package/src/providers/ai/OllamaAiProvider.ts +4 -53
- package/src/providers/ai/OpenAiProvider.ts +4 -50
- package/test/DeepSeekProvider.test.ts +94 -0
- package/test/GoogleAiProvider.test.ts +17 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/types/config/AiConfig.d.ts +6 -0
- package/types/config/AiConfig.d.ts.map +1 -0
- package/types/controllers/AICrudController.d.ts +1 -1
- package/types/factory/ai/AiProviderFactory.d.ts.map +1 -1
- package/types/index.d.ts +2 -1
- package/types/index.d.ts.map +1 -1
- package/types/providers/ai/AbstractAiProvider.d.ts +11 -1
- package/types/providers/ai/AbstractAiProvider.d.ts.map +1 -1
- package/types/providers/ai/DeepSeekAiProvider.d.ts +65 -2
- package/types/providers/ai/DeepSeekAiProvider.d.ts.map +1 -1
- package/types/providers/ai/GoogleAiProvider.d.ts.map +1 -1
- package/types/providers/ai/OllamaAiProvider.d.ts +0 -2
- package/types/providers/ai/OllamaAiProvider.d.ts.map +1 -1
- package/types/providers/ai/OpenAiProvider.d.ts +0 -2
- package/types/providers/ai/OpenAiProvider.d.ts.map +1 -1
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
IAIProvider,
|
|
4
4
|
IPromptParams,
|
|
5
5
|
IPromptResponse,
|
|
6
|
+
IPromptTool,
|
|
6
7
|
} from "../../interfaces/IAIProvider.js";
|
|
7
8
|
import type {AILogService} from "../../services/AILogService.js";
|
|
8
9
|
|
|
@@ -30,6 +31,74 @@ abstract class AbstractAiProvider implements IAIProvider {
|
|
|
30
31
|
|
|
31
32
|
abstract prompt(input: IPromptParams): Promise<IPromptResponse>
|
|
32
33
|
|
|
34
|
+
protected buildSystemPrompt(input: IPromptParams){
|
|
35
|
+
if(!input.systemPrompt){
|
|
36
|
+
throw new Error("systemPrompt required")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let systemPrompt = input.systemPrompt
|
|
40
|
+
|
|
41
|
+
if(input.memory && input.memory.length > 0){
|
|
42
|
+
systemPrompt += `\n\n ${input.memoryHeader ?? '[MEMORIA]'}\n ${input.memory.map(m => `${m.key}: ${m.value}`).join('\n')}`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if(input.knowledgeBase && input.knowledgeBase.length > 0){
|
|
46
|
+
systemPrompt += `\n\n${input.knowledgeBaseHeader ?? '[BASE DE CONOCIMIENTO]'}\n ${input.knowledgeBase.join('\n')}`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return systemPrompt
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
protected resolvePromptModel(input: IPromptParams, model: string, visionModel?: string){
|
|
53
|
+
return input.model ?? (this.hasImageInput(input) ? visionModel ?? model : model)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
protected getDefaultToolParameters(){
|
|
57
|
+
return {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {},
|
|
60
|
+
additionalProperties: false,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
protected findToolOrThrow(toolName: string | undefined, tools: IPromptTool[] = []){
|
|
65
|
+
const tool = tools.find(t => t.name === toolName)
|
|
66
|
+
|
|
67
|
+
if(!tool){
|
|
68
|
+
throw new Error(`Tool not found: ${toolName}`)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return tool
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
protected parseToolArguments(args: string | object | undefined){
|
|
75
|
+
if(!args){
|
|
76
|
+
return {}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if(typeof args === "object"){
|
|
80
|
+
return args
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try{
|
|
84
|
+
return JSON.parse(args)
|
|
85
|
+
}catch(e){
|
|
86
|
+
throw new Error(`Invalid tool arguments: ${args}`)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
protected serializeToolOutput(output: unknown){
|
|
91
|
+
if(typeof output === "string"){
|
|
92
|
+
return output
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if(output === undefined){
|
|
96
|
+
return ""
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return JSON.stringify(output)
|
|
100
|
+
}
|
|
101
|
+
|
|
33
102
|
protected hasImageInput(input: IPromptParams){
|
|
34
103
|
if(input.userImages && input.userImages.length > 0){
|
|
35
104
|
return true
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
|
+
import {toJSONSchema} from "zod";
|
|
3
|
+
import type {
|
|
4
|
+
IPromptContentPart,
|
|
5
|
+
IPromptMessage,
|
|
6
|
+
IPromptParams,
|
|
7
|
+
IPromptResponse,
|
|
8
|
+
IPromptTool
|
|
9
|
+
} from "../../interfaces/IAIProvider.js";
|
|
2
10
|
import type {AILogService} from "../../services/AILogService.js";
|
|
3
|
-
import
|
|
11
|
+
import PromptAudioService from "../../services/PromptAudioService.js";
|
|
12
|
+
import AbstractAiProvider from "./AbstractAiProvider.js";
|
|
4
13
|
|
|
5
|
-
class DeepSeekAiProvider extends
|
|
14
|
+
class DeepSeekAiProvider extends AbstractAiProvider{
|
|
15
|
+
protected _apiKey: string
|
|
16
|
+
protected _model: any
|
|
6
17
|
protected _baseUrl: string
|
|
18
|
+
protected _visionModel?: string
|
|
19
|
+
protected _client: any
|
|
7
20
|
|
|
8
21
|
constructor(apiKey: string, model: string, baseUrl: string = "https://api.deepseek.com", visionModel?: string, aiLogService?: AILogService) {
|
|
9
22
|
if (!apiKey) {
|
|
@@ -12,14 +25,23 @@ class DeepSeekAiProvider extends OpenAiProvider{
|
|
|
12
25
|
if (!model) {
|
|
13
26
|
throw new Error("DeepSeek model required")
|
|
14
27
|
}
|
|
15
|
-
|
|
16
|
-
super(apiKey, model, visionModel, aiLogService, "deepseek")
|
|
17
|
-
|
|
18
28
|
if (!baseUrl) {
|
|
19
29
|
throw new Error("DeepSeek baseUrl required")
|
|
20
30
|
}
|
|
21
31
|
|
|
32
|
+
super("deepseek", aiLogService)
|
|
33
|
+
|
|
34
|
+
this._apiKey = apiKey
|
|
35
|
+
this._model = model
|
|
22
36
|
this._baseUrl = baseUrl
|
|
37
|
+
this._visionModel = visionModel
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get model(){
|
|
41
|
+
if(!this._model){
|
|
42
|
+
throw new Error("DeepSeek model not found")
|
|
43
|
+
}
|
|
44
|
+
return this._model;
|
|
23
45
|
}
|
|
24
46
|
|
|
25
47
|
get client(){
|
|
@@ -33,6 +55,242 @@ class DeepSeekAiProvider extends OpenAiProvider{
|
|
|
33
55
|
return this._client
|
|
34
56
|
}
|
|
35
57
|
|
|
58
|
+
protected get visionModel(){
|
|
59
|
+
return this._visionModel
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
protected buildUserContent(input: IPromptParams): string | Array<{type: 'text', text: string} | {type: 'image_url', image_url: {url: string, detail?: 'auto' | 'low' | 'high'}}> {
|
|
63
|
+
if(input.userContent && input.userContent.length > 0){
|
|
64
|
+
return this.mapContentParts(input.userContent)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if(input.userImages && input.userImages.length > 0){
|
|
68
|
+
const content: Array<{type: 'text', text: string} | {type: 'image_url', image_url: {url: string, detail?: 'auto' | 'low' | 'high'}}> = []
|
|
69
|
+
|
|
70
|
+
if(input.userInput){
|
|
71
|
+
content.push({type: 'text', text: input.userInput})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
content.push(...input.userImages.map(image => ({
|
|
75
|
+
type: 'image_url' as const,
|
|
76
|
+
image_url: {
|
|
77
|
+
url: image.url,
|
|
78
|
+
...(image.detail ? {detail: image.detail} : {}),
|
|
79
|
+
}
|
|
80
|
+
})))
|
|
81
|
+
|
|
82
|
+
return content
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return input.userInput ?? ""
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
protected mapContentParts(content: IPromptContentPart[]){
|
|
89
|
+
return content.map(part => {
|
|
90
|
+
if(part.type === 'text'){
|
|
91
|
+
return {
|
|
92
|
+
type: 'text' as const,
|
|
93
|
+
text: part.text
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
type: 'image_url' as const,
|
|
99
|
+
image_url: {
|
|
100
|
+
url: part.imageUrl,
|
|
101
|
+
...(part.detail ? {detail: part.detail} : {}),
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
protected mapHistory(history: IPromptMessage[] = []){
|
|
108
|
+
return history.map(message => ({
|
|
109
|
+
role: message.role,
|
|
110
|
+
content: typeof message.content === 'string'
|
|
111
|
+
? message.content
|
|
112
|
+
: this.mapContentParts(message.content)
|
|
113
|
+
}))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async generateEmbedding({text, model="text-embedding-ada-002"}: {text:string,model:string }): Promise<number[]> {
|
|
117
|
+
const response = await this.client.embeddings.create({
|
|
118
|
+
model: model,
|
|
119
|
+
input: text,
|
|
120
|
+
});
|
|
121
|
+
return response.data[0].embedding;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
protected mapTools(tools: IPromptTool[] = []){
|
|
125
|
+
return tools.map(tool => ({
|
|
126
|
+
type: "function" as const,
|
|
127
|
+
function: {
|
|
128
|
+
name: tool.name,
|
|
129
|
+
description: tool.description,
|
|
130
|
+
parameters: tool.parameters ?? this.getDefaultToolParameters(),
|
|
131
|
+
},
|
|
132
|
+
}))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
protected normalizeJsonSchema(input: IPromptParams){
|
|
136
|
+
if(input.zodSchema){
|
|
137
|
+
return toJSONSchema(input.zodSchema, {
|
|
138
|
+
target: "draft-7",
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if(!input.jsonSchema){
|
|
143
|
+
return undefined
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const jsonSchema: any = input.jsonSchema
|
|
147
|
+
|
|
148
|
+
if(jsonSchema.type === "json_schema" && jsonSchema.json_schema?.schema){
|
|
149
|
+
return jsonSchema.json_schema.schema
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if(jsonSchema.type === "json_object"){
|
|
153
|
+
return undefined
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return jsonSchema
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
protected normalizeResponseFormat(input: IPromptParams){
|
|
160
|
+
if(input.zodSchema || input.jsonSchema){
|
|
161
|
+
return {type: "json_object" as const}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return undefined
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
protected buildDeepSeekSystemPrompt(input: IPromptParams){
|
|
168
|
+
const systemPrompt = this.buildSystemPrompt(input)
|
|
169
|
+
const jsonSchema = this.normalizeJsonSchema(input)
|
|
170
|
+
|
|
171
|
+
if(!jsonSchema){
|
|
172
|
+
return systemPrompt
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return `${systemPrompt}
|
|
176
|
+
|
|
177
|
+
Respond only with valid JSON matching this JSON Schema. Do not wrap the response in markdown or include any extra text.
|
|
178
|
+
|
|
179
|
+
JSON Schema:
|
|
180
|
+
${JSON.stringify(jsonSchema, null, 2)}`
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
protected async buildToolMessages(toolCalls: any[] = [], tools: IPromptTool[] = []){
|
|
184
|
+
const toolMessages: any[] = []
|
|
185
|
+
|
|
186
|
+
for(const toolCall of toolCalls){
|
|
187
|
+
const toolName = toolCall.function?.name
|
|
188
|
+
const tool = this.findToolOrThrow(toolName, tools)
|
|
189
|
+
|
|
190
|
+
const args = this.parseToolArguments(toolCall.function?.arguments)
|
|
191
|
+
const output = await tool.execute(args)
|
|
192
|
+
|
|
193
|
+
toolMessages.push({
|
|
194
|
+
role: "tool",
|
|
195
|
+
tool_call_id: toolCall.id,
|
|
196
|
+
content: this.serializeToolOutput(output),
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return toolMessages
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async prompt(input: IPromptParams): Promise<IPromptResponse> {
|
|
204
|
+
|
|
205
|
+
const systemPrompt = this.buildDeepSeekSystemPrompt(input)
|
|
206
|
+
const userInput = this.buildUserContent(input)
|
|
207
|
+
const model = this.resolvePromptModel(input, this.model, this.visionModel)
|
|
208
|
+
const startedAt = new Date()
|
|
209
|
+
const startTime = performance.now()
|
|
210
|
+
let tokens = 0
|
|
211
|
+
let inputTokens = 0
|
|
212
|
+
let outputTokens = 0
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const messages: any[] = [
|
|
216
|
+
{role: 'system', content: systemPrompt},
|
|
217
|
+
...this.mapHistory(input.history),
|
|
218
|
+
{role: 'user', content: userInput},
|
|
219
|
+
]
|
|
220
|
+
const tools = input.tools ?? []
|
|
221
|
+
const maxIterations = input.toolMaxIterations ?? 5
|
|
222
|
+
const responseFormat = this.normalizeResponseFormat(input)
|
|
223
|
+
let output: any
|
|
224
|
+
|
|
225
|
+
for(let iteration = 0; iteration < maxIterations; iteration++){
|
|
226
|
+
const chatCompletion = await this.client.chat.completions.create({
|
|
227
|
+
messages,
|
|
228
|
+
|
|
229
|
+
...(responseFormat ? {response_format: responseFormat} : {}),
|
|
230
|
+
...(tools.length > 0 ? {tools: this.mapTools(tools)} : {}),
|
|
231
|
+
model: model,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
tokens += chatCompletion.usage?.total_tokens ?? 0
|
|
235
|
+
inputTokens += chatCompletion.usage?.prompt_tokens ?? 0
|
|
236
|
+
outputTokens += chatCompletion.usage?.completion_tokens ?? 0
|
|
237
|
+
|
|
238
|
+
const message = chatCompletion.choices[0].message
|
|
239
|
+
const toolCalls = message.tool_calls ?? []
|
|
240
|
+
|
|
241
|
+
if(toolCalls.length === 0){
|
|
242
|
+
output = message.content
|
|
243
|
+
break
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
messages.push(message)
|
|
247
|
+
messages.push(...await this.buildToolMessages(toolCalls, tools))
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if(output === undefined){
|
|
251
|
+
throw new Error(`Tool max iterations reached: ${maxIterations}`)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const endTime = performance.now()
|
|
255
|
+
const time = endTime - startTime
|
|
256
|
+
const endedAt = new Date()
|
|
257
|
+
const audio = await PromptAudioService.build(input, output)
|
|
258
|
+
|
|
259
|
+
await this.registerPromptLog(input, {
|
|
260
|
+
model,
|
|
261
|
+
systemPrompt,
|
|
262
|
+
startedAt,
|
|
263
|
+
endedAt,
|
|
264
|
+
inputTokens,
|
|
265
|
+
outputTokens,
|
|
266
|
+
tokens,
|
|
267
|
+
output,
|
|
268
|
+
success: true,
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
output,
|
|
273
|
+
tokens,
|
|
274
|
+
inputTokens,
|
|
275
|
+
outputTokens,
|
|
276
|
+
time,
|
|
277
|
+
...(audio ? {audio} : {}),
|
|
278
|
+
}
|
|
279
|
+
} catch (e: any) {
|
|
280
|
+
const endedAt = new Date()
|
|
281
|
+
|
|
282
|
+
await this.registerPromptLog(input, {
|
|
283
|
+
model,
|
|
284
|
+
systemPrompt,
|
|
285
|
+
startedAt,
|
|
286
|
+
endedAt,
|
|
287
|
+
success: false,
|
|
288
|
+
errorMessage: e?.message,
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
throw e
|
|
292
|
+
}
|
|
293
|
+
}
|
|
36
294
|
}
|
|
37
295
|
|
|
38
296
|
export default DeepSeekAiProvider
|
|
@@ -185,11 +185,7 @@ class GoogleAiProvider extends AbstractAiProvider{
|
|
|
185
185
|
functionDeclarations: tools.map(tool => ({
|
|
186
186
|
name: tool.name,
|
|
187
187
|
description: tool.description,
|
|
188
|
-
parametersJsonSchema: tool.parameters ??
|
|
189
|
-
type: "object",
|
|
190
|
-
properties: {},
|
|
191
|
-
additionalProperties: false,
|
|
192
|
-
},
|
|
188
|
+
parametersJsonSchema: tool.parameters ?? this.getDefaultToolParameters(),
|
|
193
189
|
}))
|
|
194
190
|
}]
|
|
195
191
|
}
|
|
@@ -238,11 +234,7 @@ class GoogleAiProvider extends AbstractAiProvider{
|
|
|
238
234
|
|
|
239
235
|
for(const functionCall of functionCalls){
|
|
240
236
|
const toolName = functionCall.name
|
|
241
|
-
const tool =
|
|
242
|
-
|
|
243
|
-
if(!tool){
|
|
244
|
-
throw new Error(`Tool not found: ${toolName}`)
|
|
245
|
-
}
|
|
237
|
+
const tool = this.findToolOrThrow(toolName, tools)
|
|
246
238
|
|
|
247
239
|
const output = await tool.execute(functionCall.args ?? {})
|
|
248
240
|
|
|
@@ -271,23 +263,9 @@ class GoogleAiProvider extends AbstractAiProvider{
|
|
|
271
263
|
|
|
272
264
|
async prompt(input: IPromptParams): Promise<IPromptResponse> {
|
|
273
265
|
|
|
274
|
-
|
|
275
|
-
throw new Error("systemPrompt required")
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
let systemPrompt = input.systemPrompt
|
|
279
|
-
|
|
280
|
-
if(input.memory && input.memory.length > 0){
|
|
281
|
-
systemPrompt += `\n\n ${input.memoryHeader ?? '[MEMORIA]'}\n ${input.memory.map(m => `${m.key}: ${m.value}`).join('\n')}`
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if(input.knowledgeBase && input.knowledgeBase.length > 0){
|
|
285
|
-
systemPrompt += `\n\n${input.knowledgeBaseHeader ?? '[BASE DE CONOCIMIENTO]'}\n ${input.knowledgeBase.join('\n')}`
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
|
|
266
|
+
const systemPrompt = this.buildSystemPrompt(input)
|
|
289
267
|
const userInput = this.buildUserContent(input)
|
|
290
|
-
const model =
|
|
268
|
+
const model = this.resolvePromptModel(input, this.model, this.visionModel)
|
|
291
269
|
const startedAt = new Date()
|
|
292
270
|
const startTime = performance.now()
|
|
293
271
|
let tokens = 0
|
|
@@ -168,11 +168,7 @@ class OllamaAiProvider extends AbstractAiProvider{
|
|
|
168
168
|
function: {
|
|
169
169
|
name: tool.name,
|
|
170
170
|
description: tool.description,
|
|
171
|
-
parameters: tool.parameters ??
|
|
172
|
-
type: "object",
|
|
173
|
-
properties: {},
|
|
174
|
-
additionalProperties: false,
|
|
175
|
-
},
|
|
171
|
+
parameters: tool.parameters ?? this.getDefaultToolParameters(),
|
|
176
172
|
},
|
|
177
173
|
}))
|
|
178
174
|
}
|
|
@@ -197,44 +193,12 @@ class OllamaAiProvider extends AbstractAiProvider{
|
|
|
197
193
|
return jsonSchema
|
|
198
194
|
}
|
|
199
195
|
|
|
200
|
-
protected parseToolArguments(args: string | object | undefined){
|
|
201
|
-
if(!args){
|
|
202
|
-
return {}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if(typeof args === "object"){
|
|
206
|
-
return args
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
try{
|
|
210
|
-
return JSON.parse(args)
|
|
211
|
-
}catch(e){
|
|
212
|
-
throw new Error(`Invalid tool arguments: ${args}`)
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
protected serializeToolOutput(output: unknown){
|
|
217
|
-
if(typeof output === "string"){
|
|
218
|
-
return output
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if(output === undefined){
|
|
222
|
-
return ""
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return JSON.stringify(output)
|
|
226
|
-
}
|
|
227
|
-
|
|
228
196
|
protected async buildToolMessages(toolCalls: OllamaToolCall[] = [], tools: IPromptTool[] = []){
|
|
229
197
|
const toolMessages: OllamaMessage[] = []
|
|
230
198
|
|
|
231
199
|
for(const toolCall of toolCalls){
|
|
232
200
|
const toolName = toolCall.function?.name
|
|
233
|
-
const tool =
|
|
234
|
-
|
|
235
|
-
if(!tool){
|
|
236
|
-
throw new Error(`Tool not found: ${toolName}`)
|
|
237
|
-
}
|
|
201
|
+
const tool = this.findToolOrThrow(toolName, tools)
|
|
238
202
|
|
|
239
203
|
const args = this.parseToolArguments(toolCall.function?.arguments)
|
|
240
204
|
const output = await tool.execute(args)
|
|
@@ -251,21 +215,8 @@ class OllamaAiProvider extends AbstractAiProvider{
|
|
|
251
215
|
|
|
252
216
|
async prompt(input: IPromptParams): Promise<IPromptResponse> {
|
|
253
217
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
let systemPrompt = input.systemPrompt
|
|
259
|
-
|
|
260
|
-
if(input.memory && input.memory.length > 0){
|
|
261
|
-
systemPrompt += `\n\n ${input.memoryHeader ?? '[MEMORIA]'}\n ${input.memory.map(m => `${m.key}: ${m.value}`).join('\n')}`
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if(input.knowledgeBase && input.knowledgeBase.length > 0){
|
|
265
|
-
systemPrompt += `\n\n${input.knowledgeBaseHeader ?? '[BASE DE CONOCIMIENTO]'}\n ${input.knowledgeBase.join('\n')}`
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const model = input.model ?? (this.hasImageInput(input) ? this.visionModel ?? this.model : this.model)
|
|
218
|
+
const systemPrompt = this.buildSystemPrompt(input)
|
|
219
|
+
const model = this.resolvePromptModel(input, this.model, this.visionModel)
|
|
269
220
|
const startedAt = new Date()
|
|
270
221
|
const startTime = performance.now()
|
|
271
222
|
let tokens = 0
|
|
@@ -122,49 +122,17 @@ class OpenAiProvider extends AbstractAiProvider{
|
|
|
122
122
|
function: {
|
|
123
123
|
name: tool.name,
|
|
124
124
|
description: tool.description,
|
|
125
|
-
parameters: tool.parameters ??
|
|
126
|
-
type: "object",
|
|
127
|
-
properties: {},
|
|
128
|
-
additionalProperties: false,
|
|
129
|
-
},
|
|
125
|
+
parameters: tool.parameters ?? this.getDefaultToolParameters(),
|
|
130
126
|
},
|
|
131
127
|
}))
|
|
132
128
|
}
|
|
133
129
|
|
|
134
|
-
protected parseToolArguments(args: string | undefined){
|
|
135
|
-
if(!args){
|
|
136
|
-
return {}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
try{
|
|
140
|
-
return JSON.parse(args)
|
|
141
|
-
}catch(e){
|
|
142
|
-
throw new Error(`Invalid tool arguments: ${args}`)
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
protected serializeToolOutput(output: unknown){
|
|
147
|
-
if(typeof output === "string"){
|
|
148
|
-
return output
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if(output === undefined){
|
|
152
|
-
return ""
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return JSON.stringify(output)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
130
|
protected async buildToolMessages(toolCalls: any[] = [], tools: IPromptTool[] = []){
|
|
159
131
|
const toolMessages: any[] = []
|
|
160
132
|
|
|
161
133
|
for(const toolCall of toolCalls){
|
|
162
134
|
const toolName = toolCall.function?.name
|
|
163
|
-
const tool =
|
|
164
|
-
|
|
165
|
-
if(!tool){
|
|
166
|
-
throw new Error(`Tool not found: ${toolName}`)
|
|
167
|
-
}
|
|
135
|
+
const tool = this.findToolOrThrow(toolName, tools)
|
|
168
136
|
|
|
169
137
|
const args = this.parseToolArguments(toolCall.function?.arguments)
|
|
170
138
|
const output = await tool.execute(args)
|
|
@@ -181,23 +149,9 @@ class OpenAiProvider extends AbstractAiProvider{
|
|
|
181
149
|
|
|
182
150
|
async prompt(input: IPromptParams): Promise<IPromptResponse> {
|
|
183
151
|
|
|
184
|
-
|
|
185
|
-
throw new Error("systemPrompt required")
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
let systemPrompt = input.systemPrompt
|
|
189
|
-
|
|
190
|
-
if(input.memory && input.memory.length > 0){
|
|
191
|
-
systemPrompt += `\n\n ${input.memoryHeader ?? '[MEMORIA]'}\n ${input.memory.map(m => `${m.key}: ${m.value}`).join('\n')}`
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if(input.knowledgeBase && input.knowledgeBase.length > 0){
|
|
195
|
-
systemPrompt += `\n\n${input.knowledgeBaseHeader ?? '[BASE DE CONOCIMIENTO]'}\n ${input.knowledgeBase.join('\n')}`
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
152
|
+
const systemPrompt = this.buildSystemPrompt(input)
|
|
199
153
|
const userInput = this.buildUserContent(input)
|
|
200
|
-
const model =
|
|
154
|
+
const model = this.resolvePromptModel(input, this.model, this.visionModel)
|
|
201
155
|
const startedAt = new Date()
|
|
202
156
|
const startTime = performance.now()
|
|
203
157
|
let tokens = 0
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {describe, expect, test} from "vitest";
|
|
2
|
+
import {z} from "zod";
|
|
2
3
|
import {AiProviderFactory, DeepSeekAiProvider} from "../src";
|
|
3
4
|
import {IPromptTool} from "../src/interfaces/IAIProvider";
|
|
4
5
|
|
|
@@ -45,6 +46,99 @@ describe("DeepSeekProvider Test", () => {
|
|
|
45
46
|
})
|
|
46
47
|
})
|
|
47
48
|
|
|
49
|
+
test("DeepSeek prompt falls back from OpenAI json_schema to json_object with schema instructions", async () => {
|
|
50
|
+
let request: any
|
|
51
|
+
|
|
52
|
+
class MockedDeepSeekProvider extends DeepSeekAiProvider {
|
|
53
|
+
constructor() {
|
|
54
|
+
super("test-key", "deepseek-chat")
|
|
55
|
+
this._client = {
|
|
56
|
+
chat: {
|
|
57
|
+
completions: {
|
|
58
|
+
create: async (payload: any) => {
|
|
59
|
+
request = payload
|
|
60
|
+
return {
|
|
61
|
+
choices: [{message: {content: "{\"name\":\"Pikachu\"}"}}],
|
|
62
|
+
usage: {
|
|
63
|
+
total_tokens: 8,
|
|
64
|
+
prompt_tokens: 6,
|
|
65
|
+
completion_tokens: 2
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const jsonSchema = {
|
|
76
|
+
type: "json_schema",
|
|
77
|
+
json_schema: {
|
|
78
|
+
name: "element_description",
|
|
79
|
+
schema: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
name: {type: "string"}
|
|
83
|
+
},
|
|
84
|
+
required: ["name"]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const deepSeek = new MockedDeepSeekProvider()
|
|
90
|
+
await deepSeek.prompt({
|
|
91
|
+
systemPrompt: "You are an AI assistant.",
|
|
92
|
+
userInput: "What is the most famous pokemon?",
|
|
93
|
+
jsonSchema,
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
expect(request.response_format).toEqual({type: "json_object"})
|
|
97
|
+
expect(request.messages[0].content).toContain("Respond only with valid JSON")
|
|
98
|
+
expect(request.messages[0].content).toContain("\"required\": [")
|
|
99
|
+
expect(request.messages[0].content).toContain("\"name\"")
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test("DeepSeek prompt falls back from zod schema to json_object with generated schema instructions", async () => {
|
|
103
|
+
let request: any
|
|
104
|
+
|
|
105
|
+
class MockedDeepSeekProvider extends DeepSeekAiProvider {
|
|
106
|
+
constructor() {
|
|
107
|
+
super("test-key", "deepseek-chat")
|
|
108
|
+
this._client = {
|
|
109
|
+
chat: {
|
|
110
|
+
completions: {
|
|
111
|
+
create: async (payload: any) => {
|
|
112
|
+
request = payload
|
|
113
|
+
return {
|
|
114
|
+
choices: [{message: {content: "{\"name\":\"Pikachu\"}"}}],
|
|
115
|
+
usage: {
|
|
116
|
+
total_tokens: 8,
|
|
117
|
+
prompt_tokens: 6,
|
|
118
|
+
completion_tokens: 2
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const deepSeek = new MockedDeepSeekProvider()
|
|
129
|
+
await deepSeek.prompt({
|
|
130
|
+
systemPrompt: "You are an AI assistant.",
|
|
131
|
+
userInput: "What is the most famous pokemon?",
|
|
132
|
+
zodSchema: z.object({
|
|
133
|
+
name: z.string(),
|
|
134
|
+
}),
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
expect(request.response_format).toEqual({type: "json_object"})
|
|
138
|
+
expect(request.messages[0].content).toContain("JSON Schema")
|
|
139
|
+
expect(request.messages[0].content).toContain("\"name\"")
|
|
140
|
+
})
|
|
141
|
+
|
|
48
142
|
test("DeepSeek prompt executes tools through OpenAI-compatible tool calls", async () => {
|
|
49
143
|
const requests: any[] = []
|
|
50
144
|
const weatherTool: IPromptTool = {
|