@saltcorn/large-language-model 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/constants.js CHANGED
@@ -7,9 +7,10 @@ const OPENAI_MODELS = [
7
7
  "gpt-5",
8
8
  "gpt-5-mini",
9
9
  "gpt-5-nano",
10
- "gpt-5.1",
10
+ "gpt-5.1",
11
11
  "gpt-5.2",
12
12
  "gpt-5.2-pro",
13
+ "gpt-5.4",
13
14
  "o3",
14
15
  "o3-mini",
15
16
  "o3-pro",
@@ -21,6 +22,20 @@ const OPENAI_MODELS = [
21
22
  "gpt-5.1-codex-max",
22
23
  ];
23
24
 
25
+ const NO_TEMP_MODELS = [
26
+ "o1",
27
+ "o3",
28
+ "o3-mini",
29
+ "o4-mini",
30
+ "gpt-5",
31
+ "gpt-5-nano",
32
+ "gpt-5-mini",
33
+ "gpt-5.1",
34
+ "gpt-5.1-codex",
35
+ "gpt-5.2",
36
+ "gpt-5.4",
37
+ ];
38
+
24
39
  // https://github.com/ollama/ollama/blob/main/docs/faq.md#where-are-models-stored
25
40
  const OLLAMA_MODELS_PATH = {
26
41
  Darwin: `${process.env.HOME}/.ollama/models`,
@@ -28,4 +43,4 @@ const OLLAMA_MODELS_PATH = {
28
43
  Windows_NT: "C:\\Users\\%username%\\.ollama\\models.",
29
44
  };
30
45
 
31
- module.exports = { OPENAI_MODELS, OLLAMA_MODELS_PATH };
46
+ module.exports = { OPENAI_MODELS, OLLAMA_MODELS_PATH, NO_TEMP_MODELS };
package/generate.js CHANGED
@@ -23,8 +23,10 @@ const {
23
23
  experimental_transcribe,
24
24
  } = require("ai");
25
25
  const { createOpenAI } = require("@ai-sdk/openai");
26
+ const { createAnthropic } = require("@ai-sdk/anthropic");
26
27
  const OpenAI = require("openai");
27
28
  const { ElevenLabsClient } = require("@elevenlabs/elevenlabs-js");
29
+ const { NO_TEMP_MODELS } = require("./constants");
28
30
 
29
31
  let ollamaMod;
30
32
  if (features.esm_plugins) ollamaMod = require("ollama");
@@ -291,11 +293,64 @@ const toolResponse = async (
291
293
  }
292
294
  };
293
295
 
296
+ const addImageMesssage = async (
297
+ { backend, apiKey, api_key, provider, ai_sdk_provider, responses_api },
298
+ opts,
299
+ ) => {
300
+ let chat = opts.chat;
301
+ let result = opts.prompt;
302
+ //console.log("chat", JSON.stringify(chat, null, 2));
303
+ let imageurl = opts.prompt;
304
+ switch (opts.backend || backend) {
305
+ case "OpenAI":
306
+ {
307
+ const new_chat_item = responses_api
308
+ ? {
309
+ role: "user",
310
+ content: [
311
+ {
312
+ type: "input_image",
313
+ image_url: imageurl,
314
+ },
315
+ ],
316
+ }
317
+ : {
318
+ role: "user",
319
+ content: [
320
+ {
321
+ type: "image_url",
322
+ image_url: {
323
+ url: imageurl,
324
+ },
325
+ },
326
+ ],
327
+ };
328
+
329
+ chat.push(new_chat_item);
330
+ }
331
+ break;
332
+ case "AI SDK":
333
+ chat.push({
334
+ role: "user",
335
+ content: [
336
+ {
337
+ type: "image",
338
+ image: imageurl,
339
+ },
340
+ ],
341
+ });
342
+
343
+ break;
344
+ default:
345
+ }
346
+ };
347
+
294
348
  const getCompletion = async (config, opts) => {
295
349
  switch (config.backend) {
296
350
  case "AI SDK":
297
351
  return await getCompletionAISDK(
298
352
  {
353
+ ...config,
299
354
  provider: config.ai_sdk_provider,
300
355
  apiKey: config.api_key,
301
356
  model: opts?.model || config.model,
@@ -368,18 +423,28 @@ const getCompletion = async (config, opts) => {
368
423
  }
369
424
  };
370
425
 
371
- const getAiSdkModel = ({ provider, api_key, model_name }) => {
426
+ const getAiSdkModel = ({
427
+ provider,
428
+ api_key,
429
+ model_name,
430
+ anthropic_api_key,
431
+ }) => {
372
432
  switch (provider) {
373
433
  case "OpenAI":
374
434
  const openai = createOpenAI({ apiKey: api_key });
375
435
  return openai(model_name);
436
+ case "Anthropic":
437
+ const anthropic = createAnthropic({
438
+ apiKey: anthropic_api_key,
439
+ });
440
+ return anthropic(model_name);
376
441
  default:
377
442
  throw new Error("Provider not found: " + provider);
378
443
  }
379
444
  };
380
445
 
381
446
  const getCompletionAISDK = async (
382
- { apiKey, model, provider, temperature },
447
+ { apiKey, model, provider, temperature, anthropic_api_key },
383
448
  {
384
449
  systemPrompt,
385
450
  prompt,
@@ -397,6 +462,7 @@ const getCompletionAISDK = async (
397
462
  model_name: use_model_name,
398
463
  api_key: api_key || apiKey,
399
464
  provider,
465
+ anthropic_api_key,
400
466
  });
401
467
  const modifyChat = (chat) => {
402
468
  const f = (c) => {
@@ -429,27 +495,15 @@ const getCompletionAISDK = async (
429
495
  if (appendToChat && chat && prompt) {
430
496
  chat.push({ role: "user", content: prompt });
431
497
  }
432
- if (rest.temperature || temperature) {
498
+ if (NO_TEMP_MODELS.includes(use_model_name)) {
499
+ delete body.temperature;
500
+ } else if (rest.temperature || temperature) {
433
501
  const str_or_num = rest.temperature || temperature;
434
502
  body.temperature = +str_or_num;
435
503
  } else if (rest.temperature === null) {
436
504
  delete body.temperature;
437
505
  } else if (typeof temperature === "undefined") {
438
- if (
439
- ![
440
- "o1",
441
- "o3",
442
- "o3-mini",
443
- "o4-mini",
444
- "gpt-5",
445
- "gpt-5-nano",
446
- "gpt-5-mini",
447
- "gpt-5.1",
448
- "gpt-5.1-codex",
449
- "gpt-5.2",
450
- ].includes(use_model_name)
451
- )
452
- body.temperature = 0.7;
506
+ if (!NO_TEMP_MODELS.includes(use_model_name)) body.temperature = 0.7;
453
507
  }
454
508
  if (body.tools) {
455
509
  const prevTools = [...body.tools];
@@ -546,18 +600,7 @@ const getCompletionOpenAICompatible = async (
546
600
  } else if (rest.temperature === null) {
547
601
  delete body.temperature;
548
602
  } else if (typeof temperature === "undefined") {
549
- if (
550
- ![
551
- "o1",
552
- "o3",
553
- "o3-mini",
554
- "o4-mini",
555
- "gpt-5",
556
- "gpt-5-nano",
557
- "gpt-5-mini",
558
- ].includes(use_model)
559
- )
560
- body.temperature = 0.7;
603
+ if (!NO_TEMP_MODELS.includes(use_model)) body.temperature = 0.7;
561
604
  }
562
605
  if (rest.streamCallback && global.fetch) {
563
606
  body.stream = true;
@@ -1110,4 +1153,5 @@ module.exports = {
1110
1153
  getImageGeneration,
1111
1154
  getAudioTranscription,
1112
1155
  toolResponse,
1156
+ addImageMesssage,
1113
1157
  };
package/index.js CHANGED
@@ -11,7 +11,8 @@ const {
11
11
  getEmbedding,
12
12
  getImageGeneration,
13
13
  getAudioTranscription,
14
- toolResponse
14
+ toolResponse,
15
+ addImageMesssage,
15
16
  } = require("./generate");
16
17
  const { OPENAI_MODELS } = require("./constants.js");
17
18
  const { eval_expression } = require("@saltcorn/data/models/expression");
@@ -81,7 +82,7 @@ ${domReady(`
81
82
  required: true,
82
83
  showIf: { backend: "AI SDK" },
83
84
  attributes: {
84
- options: ["OpenAI"],
85
+ options: ["OpenAI", "Anthropic"],
85
86
  },
86
87
  },
87
88
  {
@@ -92,6 +93,14 @@ ${domReady(`
92
93
  fieldview: "password",
93
94
  showIf: { backend: "AI SDK", ai_sdk_provider: "OpenAI" },
94
95
  },
96
+ {
97
+ name: "anthropic_api_key",
98
+ label: "API key",
99
+ type: "String",
100
+ required: true,
101
+ fieldview: "password",
102
+ showIf: { backend: "AI SDK", ai_sdk_provider: "Anthropic" },
103
+ },
95
104
  {
96
105
  name: "model",
97
106
  label: "Model", //gpt-3.5-turbo
@@ -99,7 +108,17 @@ ${domReady(`
99
108
  required: true,
100
109
  showIf: { backend: "AI SDK" },
101
110
  attributes: {
102
- calcOptions: ["ai_sdk_provider", { OpenAI: OPENAI_MODELS }],
111
+ calcOptions: [
112
+ "ai_sdk_provider",
113
+ {
114
+ OpenAI: OPENAI_MODELS,
115
+ Anthropic: [
116
+ "claude-opus-4-6",
117
+ "claude-sonnet-4-6",
118
+ "claude-haiku-4-5",
119
+ ],
120
+ },
121
+ ],
103
122
  },
104
123
  },
105
124
  {
@@ -117,6 +136,14 @@ ${domReady(`
117
136
  "text-embedding-3-large",
118
137
  "text-embedding-ada-002",
119
138
  ],
139
+ Anthropic: [
140
+ "voyage-3-large",
141
+ "voyage-3",
142
+ "voyage-3-lite",
143
+ "voyage-code-3",
144
+ "voyage-finance-2",
145
+ "voyage-law-2",
146
+ ],
120
147
  },
121
148
  ],
122
149
  },
@@ -422,14 +449,26 @@ const functions = (config) => {
422
449
  { name: "options", type: "JSON", tstype: "any", required: true },
423
450
  ],
424
451
  },
425
- llm_add_tool_response: {
426
- run: async (prompt, opts) => {
427
- const result = await toolResponse(config, { prompt, ...opts });
428
- return result;
452
+ llm_add_message: {
453
+ run: async (what, prompt, opts) => {
454
+ switch (what) {
455
+ case "tool_response":
456
+ return await toolResponse(config, { prompt, ...opts });
457
+ case "image":
458
+ return await addImageMesssage(config, { prompt, ...opts });
459
+ default:
460
+ break;
461
+ }
429
462
  },
430
463
  isAsync: true,
431
- description: "Insert the response to a tool call into a chat",
464
+ description: "Insert a tool response or an image in a chat",
432
465
  arguments: [
466
+ {
467
+ name: "what",
468
+ type: "String",
469
+ tstype: '"tool_response"|"image"',
470
+ required: true,
471
+ },
433
472
  { name: "prompt", type: "String", required: true },
434
473
  { name: "options", type: "JSON", tstype: "any" },
435
474
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/large-language-model",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Large language models and functionality for Saltcorn",
5
5
  "main": "index.js",
6
6
  "dependencies": {
@@ -13,6 +13,7 @@
13
13
  "googleapis": "^144.0.0",
14
14
  "ai": "5.0.44",
15
15
  "@ai-sdk/openai": "2.0.30",
16
+ "@ai-sdk/anthropic": "2.0.70",
16
17
  "openai": "6.16.0",
17
18
  "@elevenlabs/elevenlabs-js": "2.31.0"
18
19
  },
package/tests/configs.js CHANGED
@@ -31,4 +31,14 @@ module.exports = [
31
31
  temperature: 0.7,
32
32
  ai_sdk_provider: "OpenAI",
33
33
  },
34
+ {
35
+ name: "AI SDK Anthropic",
36
+ model: "claude-sonnet-4-6",
37
+ api_key: process.env.ANTHROPIC_API_KEY,
38
+ backend: "AI SDK",
39
+ embed_model: "text-embedding-3-small",
40
+ image_model: "gpt-image-1",
41
+ temperature: 0.7,
42
+ ai_sdk_provider: "Anthropic",
43
+ },
34
44
  ];
package/tests/llm.test.js CHANGED
@@ -133,7 +133,12 @@ for (const nameconfig of require("./configs")) {
133
133
  const chat = [];
134
134
  const answer = await getState().functions.llm_generate.run(
135
135
  "Generate a list of EU capitals in a structured format using the provided tool",
136
- { chat, appendToChat: true, ...cities_tool, streamCallback() {} },
136
+ {
137
+ chat,
138
+ appendToChat: true,
139
+ ...cities_tool,
140
+ //streamCallback() {}
141
+ },
137
142
  );
138
143
  expect(typeof answer).toBe("object");
139
144
 
@@ -142,10 +147,14 @@ for (const nameconfig of require("./configs")) {
142
147
  const cities = tc.input.cities;
143
148
  expect(cities.length).toBe(27);
144
149
 
145
- await getState().functions.llm_add_tool_response.run("List received", {
146
- chat,
147
- tool_call: tc,
148
- });
150
+ await getState().functions.llm_add_message.run(
151
+ "tool_response",
152
+ "List received",
153
+ {
154
+ chat,
155
+ tool_call: tc,
156
+ },
157
+ );
149
158
 
150
159
  const answer1 = await getState().functions.llm_generate.run(
151
160
  "Make the same list in a structured format using the provided tool but for the original 12 member countries of the EU",