@saltcorn/large-language-model 1.0.3 → 1.0.5
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 +124 -7
- package/package.json +5 -5
- package/tests/llm.test.js +25 -3
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,
|
|
@@ -518,6 +519,14 @@ const getCompletionAISDK = async (
|
|
|
518
519
|
});
|
|
519
520
|
});
|
|
520
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
|
+
}
|
|
521
530
|
|
|
522
531
|
const debugRequest = { ...body, model: use_model_name };
|
|
523
532
|
if (debugResult)
|
|
@@ -611,13 +620,28 @@ const getCompletionOpenAICompatible = async (
|
|
|
611
620
|
}
|
|
612
621
|
if (responses_api) {
|
|
613
622
|
delete body.tool_choice;
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
tool
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
623
|
+
if (body.tools) {
|
|
624
|
+
const newtools = JSON.parse(JSON.stringify(body.tools))
|
|
625
|
+
for (const tool of newtools) {
|
|
626
|
+
if (tool.type !== "function" || !tool.function) continue;
|
|
627
|
+
tool.name = tool.function.name;
|
|
628
|
+
tool.description = tool.function.description;
|
|
629
|
+
tool.parameters = tool.function.parameters;
|
|
630
|
+
if (tool.function.required) tool.required = tool.function.required;
|
|
631
|
+
delete tool.function;
|
|
632
|
+
}
|
|
633
|
+
body.tools = newtools
|
|
634
|
+
}
|
|
635
|
+
if (body.response_format?.type === "json_schema" && !body.text) {
|
|
636
|
+
body.text = {
|
|
637
|
+
format: {
|
|
638
|
+
type: "json_schema",
|
|
639
|
+
name: body.response_format.json_schema.name,
|
|
640
|
+
//strict: true,
|
|
641
|
+
schema: lockDownSchema(body.response_format.json_schema.schema),
|
|
642
|
+
},
|
|
643
|
+
};
|
|
644
|
+
delete body.response_format;
|
|
621
645
|
}
|
|
622
646
|
let newChat;
|
|
623
647
|
if (!appendToChat) {
|
|
@@ -1148,6 +1172,99 @@ const getEmbeddingGoogleVertex = async (config, opts, oauth2Client) => {
|
|
|
1148
1172
|
return embeddings;
|
|
1149
1173
|
};
|
|
1150
1174
|
|
|
1175
|
+
function lockDownSchema(schema) {
|
|
1176
|
+
if (!schema || typeof schema !== "object") return schema;
|
|
1177
|
+
|
|
1178
|
+
// Handle arrays (e.g., allOf, oneOf, anyOf, items as array)
|
|
1179
|
+
if (Array.isArray(schema)) {
|
|
1180
|
+
schema.forEach((item) => lockDownSchema(item));
|
|
1181
|
+
return schema;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// If this subschema defines properties, lock it down
|
|
1185
|
+
if (schema.properties) {
|
|
1186
|
+
schema.additionalProperties = false;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// Recurse into properties
|
|
1190
|
+
if (schema.properties) {
|
|
1191
|
+
for (const key of Object.keys(schema.properties)) {
|
|
1192
|
+
lockDownSchema(schema.properties[key]);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// Recurse into additionalProperties if it's a schema (not just a boolean)
|
|
1197
|
+
if (
|
|
1198
|
+
schema.additionalProperties &&
|
|
1199
|
+
typeof schema.additionalProperties === "object"
|
|
1200
|
+
) {
|
|
1201
|
+
lockDownSchema(schema.additionalProperties);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
// Recurse into patternProperties
|
|
1205
|
+
if (schema.patternProperties) {
|
|
1206
|
+
for (const key of Object.keys(schema.patternProperties)) {
|
|
1207
|
+
lockDownSchema(schema.patternProperties[key]);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// Recurse into composition keywords
|
|
1212
|
+
for (const keyword of ["allOf", "oneOf", "anyOf"]) {
|
|
1213
|
+
if (Array.isArray(schema[keyword])) {
|
|
1214
|
+
schema[keyword].forEach((sub) => lockDownSchema(sub));
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// Recurse into not
|
|
1219
|
+
if (schema.not) {
|
|
1220
|
+
lockDownSchema(schema.not);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// Recurse into if/then/else
|
|
1224
|
+
for (const keyword of ["if", "then", "else"]) {
|
|
1225
|
+
if (schema[keyword]) {
|
|
1226
|
+
lockDownSchema(schema[keyword]);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// Recurse into items (tuple or single schema)
|
|
1231
|
+
if (schema.items) {
|
|
1232
|
+
if (Array.isArray(schema.items)) {
|
|
1233
|
+
schema.items.forEach((item) => lockDownSchema(item));
|
|
1234
|
+
} else {
|
|
1235
|
+
lockDownSchema(schema.items);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
// Recurse into prefixItems (Draft 2020-12)
|
|
1240
|
+
if (Array.isArray(schema.prefixItems)) {
|
|
1241
|
+
schema.prefixItems.forEach((item) => lockDownSchema(item));
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// Recurse into $defs / definitions
|
|
1245
|
+
for (const defsKey of ["$defs", "definitions"]) {
|
|
1246
|
+
if (schema[defsKey]) {
|
|
1247
|
+
for (const key of Object.keys(schema[defsKey])) {
|
|
1248
|
+
lockDownSchema(schema[defsKey][key]);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// Recurse into dependentSchemas
|
|
1254
|
+
if (schema.dependentSchemas) {
|
|
1255
|
+
for (const key of Object.keys(schema.dependentSchemas)) {
|
|
1256
|
+
lockDownSchema(schema.dependentSchemas[key]);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// Recurse into contains
|
|
1261
|
+
if (schema.contains) {
|
|
1262
|
+
lockDownSchema(schema.contains);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
return schema;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1151
1268
|
module.exports = {
|
|
1152
1269
|
getCompletion,
|
|
1153
1270
|
getEmbedding,
|
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.5",
|
|
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
|
},
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"jest": "^29.7.0"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
|
-
"test": "jest tests --runInBand"
|
|
24
|
+
"test": "jest tests --runInBand --verbose"
|
|
25
25
|
},
|
|
26
26
|
"author": "Tom Nielsen",
|
|
27
27
|
"license": "MIT",
|
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?",
|
|
@@ -98,7 +97,7 @@ for (const nameconfig of require("./configs")) {
|
|
|
98
97
|
});
|
|
99
98
|
it("uses tools", async () => {
|
|
100
99
|
const answer = await getState().functions.llm_generate.run(
|
|
101
|
-
"Generate a list of EU capitals in a structured format using the provided tool",
|
|
100
|
+
"Generate a list of all the EU capitals in a structured format using the provided tool",
|
|
102
101
|
cities_tool,
|
|
103
102
|
);
|
|
104
103
|
expect(typeof answer).toBe("object");
|
|
@@ -132,7 +131,7 @@ for (const nameconfig of require("./configs")) {
|
|
|
132
131
|
it("tool use sequence", async () => {
|
|
133
132
|
const chat = [];
|
|
134
133
|
const answer = await getState().functions.llm_generate.run(
|
|
135
|
-
"Generate a list of EU capitals in a structured format using the provided tool",
|
|
134
|
+
"Generate a list of all the EU capitals in a structured format using the provided tool",
|
|
136
135
|
{
|
|
137
136
|
chat,
|
|
138
137
|
appendToChat: true,
|
|
@@ -165,6 +164,27 @@ 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 all the 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
|
+
|
|
182
|
+
const json_answer = JSON.parse(answer);
|
|
183
|
+
|
|
184
|
+
expect(json_answer.cities.length).toBe(27);
|
|
185
|
+
expect(!!json_answer.cities[0].city_name).toBe(true);
|
|
186
|
+
expect(!!json_answer.cities[0].country_name).toBe(true);
|
|
187
|
+
});
|
|
168
188
|
if (name !== "AI SDK Anthropic")
|
|
169
189
|
it("gets embedding", async () => {
|
|
170
190
|
const v = await getState().functions.llm_embedding.run(
|
|
@@ -186,11 +206,13 @@ const cities_tool = {
|
|
|
186
206
|
description: "Provide a list of cities by country and city name",
|
|
187
207
|
parameters: {
|
|
188
208
|
type: "object",
|
|
209
|
+
required: ["cities"],
|
|
189
210
|
properties: {
|
|
190
211
|
cities: {
|
|
191
212
|
type: "array",
|
|
192
213
|
items: {
|
|
193
214
|
type: "object",
|
|
215
|
+
additionalProperties: false,
|
|
194
216
|
properties: {
|
|
195
217
|
country_name: {
|
|
196
218
|
type: "string",
|