@saltcorn/large-language-model 1.0.2 → 1.0.4
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 +134 -20
- package/index.js +1 -9
- package/package.json +4 -4
- package/tests/configs.js +0 -1
- package/tests/llm.test.js +33 -1
package/generate.js
CHANGED
|
@@ -18,6 +18,7 @@ const {
|
|
|
18
18
|
streamText,
|
|
19
19
|
tool,
|
|
20
20
|
jsonSchema,
|
|
21
|
+
Output,
|
|
21
22
|
embed,
|
|
22
23
|
embedMany,
|
|
23
24
|
experimental_transcribe,
|
|
@@ -423,17 +424,20 @@ const getCompletion = async (config, opts) => {
|
|
|
423
424
|
}
|
|
424
425
|
};
|
|
425
426
|
|
|
426
|
-
const getAiSdkModel = (
|
|
427
|
-
provider,
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
anthropic_api_key,
|
|
431
|
-
}) => {
|
|
427
|
+
const getAiSdkModel = (
|
|
428
|
+
{ provider, api_key, model_name, anthropic_api_key },
|
|
429
|
+
isEmbedding,
|
|
430
|
+
) => {
|
|
432
431
|
switch (provider) {
|
|
433
432
|
case "OpenAI":
|
|
434
433
|
const openai = createOpenAI({ apiKey: api_key });
|
|
435
|
-
return
|
|
434
|
+
return isEmbedding
|
|
435
|
+
? openai.textEmbeddingModel(model_name)
|
|
436
|
+
: openai(model_name);
|
|
437
|
+
|
|
436
438
|
case "Anthropic":
|
|
439
|
+
if (isEmbedding)
|
|
440
|
+
throw new Error("Anthropic does not provide embedding models");
|
|
437
441
|
const anthropic = createAnthropic({
|
|
438
442
|
apiKey: anthropic_api_key,
|
|
439
443
|
});
|
|
@@ -515,6 +519,14 @@ const getCompletionAISDK = async (
|
|
|
515
519
|
});
|
|
516
520
|
});
|
|
517
521
|
}
|
|
522
|
+
if (body.response_format?.type === "json_schema" && !body.output) {
|
|
523
|
+
body.output = Output.object({
|
|
524
|
+
schema: jsonSchema(
|
|
525
|
+
lockDownSchema(body.response_format.json_schema.schema),
|
|
526
|
+
),
|
|
527
|
+
});
|
|
528
|
+
delete body.response_format;
|
|
529
|
+
}
|
|
518
530
|
|
|
519
531
|
const debugRequest = { ...body, model: use_model_name };
|
|
520
532
|
if (debugResult)
|
|
@@ -616,6 +628,17 @@ const getCompletionOpenAICompatible = async (
|
|
|
616
628
|
if (tool.function.required) tool.required = tool.function.required;
|
|
617
629
|
delete tool.function;
|
|
618
630
|
}
|
|
631
|
+
if (body.response_format?.type === "json_schema" && !body.text) {
|
|
632
|
+
body.text = {
|
|
633
|
+
format: {
|
|
634
|
+
type: "json_schema",
|
|
635
|
+
name: body.response_format.json_schema.name,
|
|
636
|
+
//strict: true,
|
|
637
|
+
schema: lockDownSchema(body.response_format.json_schema.schema),
|
|
638
|
+
},
|
|
639
|
+
};
|
|
640
|
+
delete body.response_format;
|
|
641
|
+
}
|
|
619
642
|
let newChat;
|
|
620
643
|
if (!appendToChat) {
|
|
621
644
|
newChat = [];
|
|
@@ -949,19 +972,17 @@ const getEmbeddingOpenAICompatible = async (
|
|
|
949
972
|
|
|
950
973
|
const getEmbeddingAISDK = async (config, { prompt, model, debugResult }) => {
|
|
951
974
|
const { provider, apiKey, embed_model } = config;
|
|
952
|
-
let
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
break;
|
|
964
|
-
}
|
|
975
|
+
let providerOptions = {};
|
|
976
|
+
const model_name = model || embed_model || "text-embedding-3-small";
|
|
977
|
+
let model_obj = getAiSdkModel(
|
|
978
|
+
{
|
|
979
|
+
...config,
|
|
980
|
+
model_name,
|
|
981
|
+
api_key: apiKey,
|
|
982
|
+
provider,
|
|
983
|
+
},
|
|
984
|
+
true,
|
|
985
|
+
);
|
|
965
986
|
const body = {
|
|
966
987
|
model: model_obj,
|
|
967
988
|
providerOptions,
|
|
@@ -1147,6 +1168,99 @@ const getEmbeddingGoogleVertex = async (config, opts, oauth2Client) => {
|
|
|
1147
1168
|
return embeddings;
|
|
1148
1169
|
};
|
|
1149
1170
|
|
|
1171
|
+
function lockDownSchema(schema) {
|
|
1172
|
+
if (!schema || typeof schema !== "object") return schema;
|
|
1173
|
+
|
|
1174
|
+
// Handle arrays (e.g., allOf, oneOf, anyOf, items as array)
|
|
1175
|
+
if (Array.isArray(schema)) {
|
|
1176
|
+
schema.forEach((item) => lockDownSchema(item));
|
|
1177
|
+
return schema;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// If this subschema defines properties, lock it down
|
|
1181
|
+
if (schema.properties) {
|
|
1182
|
+
schema.additionalProperties = false;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// Recurse into properties
|
|
1186
|
+
if (schema.properties) {
|
|
1187
|
+
for (const key of Object.keys(schema.properties)) {
|
|
1188
|
+
lockDownSchema(schema.properties[key]);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// Recurse into additionalProperties if it's a schema (not just a boolean)
|
|
1193
|
+
if (
|
|
1194
|
+
schema.additionalProperties &&
|
|
1195
|
+
typeof schema.additionalProperties === "object"
|
|
1196
|
+
) {
|
|
1197
|
+
lockDownSchema(schema.additionalProperties);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// Recurse into patternProperties
|
|
1201
|
+
if (schema.patternProperties) {
|
|
1202
|
+
for (const key of Object.keys(schema.patternProperties)) {
|
|
1203
|
+
lockDownSchema(schema.patternProperties[key]);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Recurse into composition keywords
|
|
1208
|
+
for (const keyword of ["allOf", "oneOf", "anyOf"]) {
|
|
1209
|
+
if (Array.isArray(schema[keyword])) {
|
|
1210
|
+
schema[keyword].forEach((sub) => lockDownSchema(sub));
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
// Recurse into not
|
|
1215
|
+
if (schema.not) {
|
|
1216
|
+
lockDownSchema(schema.not);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// Recurse into if/then/else
|
|
1220
|
+
for (const keyword of ["if", "then", "else"]) {
|
|
1221
|
+
if (schema[keyword]) {
|
|
1222
|
+
lockDownSchema(schema[keyword]);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// Recurse into items (tuple or single schema)
|
|
1227
|
+
if (schema.items) {
|
|
1228
|
+
if (Array.isArray(schema.items)) {
|
|
1229
|
+
schema.items.forEach((item) => lockDownSchema(item));
|
|
1230
|
+
} else {
|
|
1231
|
+
lockDownSchema(schema.items);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// Recurse into prefixItems (Draft 2020-12)
|
|
1236
|
+
if (Array.isArray(schema.prefixItems)) {
|
|
1237
|
+
schema.prefixItems.forEach((item) => lockDownSchema(item));
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// Recurse into $defs / definitions
|
|
1241
|
+
for (const defsKey of ["$defs", "definitions"]) {
|
|
1242
|
+
if (schema[defsKey]) {
|
|
1243
|
+
for (const key of Object.keys(schema[defsKey])) {
|
|
1244
|
+
lockDownSchema(schema[defsKey][key]);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// Recurse into dependentSchemas
|
|
1250
|
+
if (schema.dependentSchemas) {
|
|
1251
|
+
for (const key of Object.keys(schema.dependentSchemas)) {
|
|
1252
|
+
lockDownSchema(schema.dependentSchemas[key]);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// Recurse into contains
|
|
1257
|
+
if (schema.contains) {
|
|
1258
|
+
lockDownSchema(schema.contains);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
return schema;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1150
1264
|
module.exports = {
|
|
1151
1265
|
getCompletion,
|
|
1152
1266
|
getEmbedding,
|
package/index.js
CHANGED
|
@@ -126,7 +126,7 @@ ${domReady(`
|
|
|
126
126
|
label: "Embedding model", //gpt-3.5-turbo
|
|
127
127
|
type: "String",
|
|
128
128
|
required: true,
|
|
129
|
-
showIf: { backend: "AI SDK" },
|
|
129
|
+
showIf: { backend: "AI SDK", ai_sdk_provider: ["OpenAI"] },
|
|
130
130
|
attributes: {
|
|
131
131
|
calcOptions: [
|
|
132
132
|
"ai_sdk_provider",
|
|
@@ -136,14 +136,6 @@ ${domReady(`
|
|
|
136
136
|
"text-embedding-3-large",
|
|
137
137
|
"text-embedding-ada-002",
|
|
138
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
|
-
],
|
|
147
139
|
},
|
|
148
140
|
],
|
|
149
141
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/large-language-model",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Large language models and functionality for Saltcorn",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"dependencies": {
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"@google-cloud/vertexai": "^1.9.3",
|
|
12
12
|
"@google-cloud/aiplatform": "^3.34.0",
|
|
13
13
|
"googleapis": "^144.0.0",
|
|
14
|
-
"ai": "
|
|
15
|
-
"@ai-sdk/openai": "
|
|
16
|
-
"@ai-sdk/anthropic": "
|
|
14
|
+
"ai": "6.0.116",
|
|
15
|
+
"@ai-sdk/openai": "3.0.41",
|
|
16
|
+
"@ai-sdk/anthropic": "3.0.58",
|
|
17
17
|
"openai": "6.16.0",
|
|
18
18
|
"@elevenlabs/elevenlabs-js": "2.31.0"
|
|
19
19
|
},
|
package/tests/configs.js
CHANGED
package/tests/llm.test.js
CHANGED
|
@@ -29,7 +29,6 @@ for (const nameconfig of require("./configs")) {
|
|
|
29
29
|
config,
|
|
30
30
|
);
|
|
31
31
|
});
|
|
32
|
-
|
|
33
32
|
it("generates text", async () => {
|
|
34
33
|
const answer = await getState().functions.llm_generate.run(
|
|
35
34
|
"What is the Capital of France?",
|
|
@@ -165,6 +164,37 @@ for (const nameconfig of require("./configs")) {
|
|
|
165
164
|
|
|
166
165
|
expect(cities1.length).toBe(12);
|
|
167
166
|
});
|
|
167
|
+
it("uses response_format", async () => {
|
|
168
|
+
const answer = await getState().functions.llm_generate.run(
|
|
169
|
+
"Generate a list of EU capitals in JSON format",
|
|
170
|
+
{
|
|
171
|
+
response_format: {
|
|
172
|
+
type: "json_schema",
|
|
173
|
+
json_schema: {
|
|
174
|
+
name: "cities",
|
|
175
|
+
schema: cities_tool.tools[0].function.parameters,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
expect(typeof answer).toBe("string");
|
|
181
|
+
console.log("answer", answer);
|
|
182
|
+
|
|
183
|
+
const json_answer = JSON.parse(answer);
|
|
184
|
+
|
|
185
|
+
expect(json_answer.cities.length).toBe(27);
|
|
186
|
+
expect(!!json_answer.cities[0].city_name).toBe(true);
|
|
187
|
+
expect(!!json_answer.cities[0].country_name).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
if (name !== "AI SDK Anthropic")
|
|
190
|
+
it("gets embedding", async () => {
|
|
191
|
+
const v = await getState().functions.llm_embedding.run(
|
|
192
|
+
"The quick brown fox jumps over the lazy dog",
|
|
193
|
+
);
|
|
194
|
+
expect(Array.isArray(v)).toBe(true);
|
|
195
|
+
expect(v.length).toBeGreaterThan(50);
|
|
196
|
+
expect(typeof v[0]).toBe("number");
|
|
197
|
+
});
|
|
168
198
|
});
|
|
169
199
|
}
|
|
170
200
|
|
|
@@ -177,11 +207,13 @@ const cities_tool = {
|
|
|
177
207
|
description: "Provide a list of cities by country and city name",
|
|
178
208
|
parameters: {
|
|
179
209
|
type: "object",
|
|
210
|
+
required: ["cities"],
|
|
180
211
|
properties: {
|
|
181
212
|
cities: {
|
|
182
213
|
type: "array",
|
|
183
214
|
items: {
|
|
184
215
|
type: "object",
|
|
216
|
+
additionalProperties: false,
|
|
185
217
|
properties: {
|
|
186
218
|
country_name: {
|
|
187
219
|
type: "string",
|