@saltcorn/large-language-model 1.0.8 → 1.0.10

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/generate.js CHANGED
@@ -331,12 +331,17 @@ const addImageMesssage = async (
331
331
  }
332
332
  break;
333
333
  case "AI SDK":
334
+ let aisdk_image;
335
+ if (imageurl.startsWith("data:") && imageurl.includes("base64,")) {
336
+ const [_pre, b64] = imageurl.split("base64,");
337
+ aisdk_image = b64;
338
+ } else aisdk_image = imageurl;
334
339
  chat.push({
335
340
  role: "user",
336
341
  content: [
337
342
  {
338
343
  type: "image",
339
- image: imageurl,
344
+ image: aisdk_image,
340
345
  },
341
346
  ],
342
347
  });
@@ -548,7 +553,12 @@ const getCompletionAISDK = async (
548
553
  const debugRequest = { ...body, model: use_model_name };
549
554
  if (debugResult)
550
555
  console.log("AI SDK request", JSON.stringify(debugRequest, null, 2));
551
- getState().log(6, `AI SDK request ${JSON.stringify(debugRequest)} `);
556
+ getState().log(
557
+ 6,
558
+ `AI SDK request`,
559
+ { tools: Object.keys(debugRequest.tools || {}), model: debugRequest.model },
560
+ JSON.stringify(debugRequest.messages, null, 2),
561
+ );
552
562
  if (debugCollector) debugCollector.request = debugRequest;
553
563
  const reqTimeStart = Date.now();
554
564
 
@@ -570,15 +580,18 @@ const getCompletionAISDK = async (
570
580
  chat.push(...results.response.messages);
571
581
  }
572
582
 
573
- if (debugResult)
574
- console.log("AI SDK response", JSON.stringify(results, null, 2));
575
- else getState().log(6, `AI SDK response ${JSON.stringify(results)}`);
576
583
  if (debugCollector) {
577
584
  debugCollector.response = results;
578
585
  debugCollector.response_time_ms = Date.now() - reqTimeStart;
579
586
  }
580
587
  const allToolCalls = (await results.steps).flatMap((step) => step.toolCalls);
581
-
588
+ if (debugResult)
589
+ console.log("AI SDK response", JSON.stringify(results, null, 2));
590
+ else
591
+ getState().log(6, `AI SDK response`, {
592
+ text: results.text,
593
+ tool_calls: allToolCalls,
594
+ });
582
595
  if (allToolCalls.length) {
583
596
  return {
584
597
  tool_calls: allToolCalls,
@@ -833,10 +846,29 @@ const getCompletionOpenAICompatible = async (
833
846
  if (streamToolCalls) debugCollector.response = streamToolCalls;
834
847
  debugCollector.response_time_ms = Date.now() - reqTimeStart;
835
848
  }
849
+ if (appendToChat && chat && streamToolCalls) {
850
+ chat.push({
851
+ role: "assistant",
852
+ content: streamParts.join("") || null,
853
+ tool_calls: streamToolCalls.tool_calls,
854
+ });
855
+ } else if (appendToChat && chat && streamParts.length > 0) {
856
+ chat.push({ role: "assistant", content: streamParts.join("") });
857
+ }
836
858
  return streamToolCalls
837
859
  ? {
838
860
  content: streamParts.join(""),
839
861
  tool_calls: streamToolCalls.tool_calls,
862
+ hasToolCalls: streamToolCalls.tool_calls?.length,
863
+ getToolCalls() {
864
+ return streamToolCalls.tool_calls.map((tc) => ({
865
+ tool_name: tc.function.name,
866
+ input: tc.function.arguments
867
+ ? JSON.parse(tc.function.arguments)
868
+ : {},
869
+ tool_call_id: tc.id,
870
+ }));
871
+ },
840
872
  }
841
873
  : streamParts.join("");
842
874
  }
@@ -890,7 +922,9 @@ const getCompletionOpenAICompatible = async (
890
922
  getToolCalls() {
891
923
  return tool_calls.map((tc) => ({
892
924
  tool_name: tc.function.name,
893
- input: JSON.parse(tc.function.arguments),
925
+ input: tc.function.arguments
926
+ ? JSON.parse(tc.function.arguments)
927
+ : {},
894
928
  tool_call_id: tc.call_id,
895
929
  }));
896
930
  },
@@ -905,7 +939,9 @@ const getCompletionOpenAICompatible = async (
905
939
  getToolCalls() {
906
940
  return results?.choices?.[0]?.message?.tool_calls.map((tc) => ({
907
941
  tool_name: tc.function.name,
908
- input: JSON.parse(tc.function.arguments),
942
+ input: tc.function.arguments
943
+ ? JSON.parse(tc.function.arguments)
944
+ : {},
909
945
  tool_call_id: tc.id,
910
946
  }));
911
947
  },
package/index.js CHANGED
@@ -658,6 +658,7 @@ module.exports = {
658
658
  functions,
659
659
  modelpatterns: require("./model.js"),
660
660
  routes,
661
+ ready_for_mobile: false,
661
662
  actions: (config) => ({
662
663
  llm_function_call: require("./function-insert-action.js")(config),
663
664
  llm_generate: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/large-language-model",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Large language models and functionality for Saltcorn",
5
5
  "main": "index.js",
6
6
  "dependencies": {
Binary file
package/tests/llm.test.js CHANGED
@@ -2,7 +2,8 @@ const { getState } = require("@saltcorn/data/db/state");
2
2
  const View = require("@saltcorn/data/models/view");
3
3
  const Table = require("@saltcorn/data/models/table");
4
4
  const Plugin = require("@saltcorn/data/models/plugin");
5
-
5
+ const fs = require("fs");
6
+ const path = require("path");
6
7
  const { mockReqRes } = require("@saltcorn/data/tests/mocks");
7
8
  const { afterAll, beforeAll, describe, it, expect } = require("@jest/globals");
8
9
 
@@ -97,14 +98,14 @@ for (const nameconfig of require("./configs")) {
97
98
  });
98
99
  it("uses tools", async () => {
99
100
  const answer = await getState().functions.llm_generate.run(
100
- "Generate a list of all the EU capitals in a structured format using the provided tool",
101
+ "Generate a list of all the EU capitals in a structured format using the cities tool",
101
102
  cities_tool,
102
103
  );
103
104
  expect(typeof answer).toBe("object");
104
- const cities = answer.ai_sdk
105
- ? answer.tool_calls[0].input?.cities
106
- : JSON.parse(answer.tool_calls[0].function.arguments).cities;
105
+ const cities = answer.getToolCalls()[0].input?.cities;
107
106
  expect(cities.length).toBe(27);
107
+ expect(typeof cities[0].country_name).toBe("string");
108
+ expect(typeof cities[0].city_name).toBe("string");
108
109
  });
109
110
  it("appends to chat history", async () => {
110
111
  const chat = [];
@@ -131,7 +132,7 @@ for (const nameconfig of require("./configs")) {
131
132
  it("tool use sequence", async () => {
132
133
  const chat = [];
133
134
  const answer = await getState().functions.llm_generate.run(
134
- "Generate a list of all the EU capitals in a structured format using the provided tool",
135
+ "Generate a list of all the EU capitals in a structured format using the cities tool",
135
136
  {
136
137
  chat,
137
138
  appendToChat: true,
@@ -185,6 +186,24 @@ for (const nameconfig of require("./configs")) {
185
186
  expect(!!json_answer.cities[0].city_name).toBe(true);
186
187
  expect(!!json_answer.cities[0].country_name).toBe(true);
187
188
  });
189
+ it("reads images", async () => {
190
+ const chat = [];
191
+ const b64 = fs
192
+ .readFileSync(path.join(__dirname, "enjoy.png"))
193
+ .toString("base64"),
194
+ imageurl = `data:image/png;base64,${b64}`;
195
+ await getState().functions.llm_add_message.run("image", imageurl, {
196
+ chat,
197
+ });
198
+ const answer = await getState().functions.llm_generate.run(
199
+ "What is written in this picture?",
200
+ {
201
+ chat,
202
+ },
203
+ );
204
+ expect(typeof answer).toBe("string");
205
+ expect(answer.toLowerCase()).toContain("coffee");
206
+ });
188
207
  if (name !== "AI SDK Anthropic")
189
208
  it("gets embedding", async () => {
190
209
  const v = await getState().functions.llm_embedding.run(