@saltcorn/copilot 0.4.3 → 0.4.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/user-copilot.js +93 -52
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/copilot",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "AI assistant for building Saltcorn applications",
5
5
  "main": "index.js",
6
6
  "dependencies": {
package/user-copilot.js CHANGED
@@ -196,6 +196,17 @@ const run = async (table_id, viewname, config, state, { res, req }) => {
196
196
  case "assistant":
197
197
  case "system":
198
198
  if (interact.tool_calls) {
199
+ if (interact.content) {
200
+ interactMarkups.push(
201
+ div(
202
+ { class: "interaction-segment" },
203
+ span({ class: "badge bg-secondary" }, "Copilot"),
204
+ typeof interact.content === "string"
205
+ ? md.render(interact.content)
206
+ : interact.content
207
+ )
208
+ );
209
+ }
199
210
  for (const tool_call of interact.tool_calls) {
200
211
  const action = config.actions.find(
201
212
  (a) => a.trigger_name === tool_call.function.name
@@ -212,17 +223,14 @@ const run = async (table_id, viewname, config, state, { res, req }) => {
212
223
  "Copilot"
213
224
  )
214
225
  );
215
- } else if (tool_call.function.name.startsWith("Query")) {
226
+ } else if (tool_call.function.name === "TableQuery") {
216
227
  const query = JSON.parse(tool_call.function.arguments);
217
228
  const queryText = query.sql_id_query
218
229
  ? query.sql_id_query
219
230
  : JSON.stringify(query, null, 2);
220
231
  interactMarkups.push(
221
232
  wrapSegment(
222
- wrapCard(
223
- "Query " + tool_call.function.name.replace("Query", ""),
224
- pre(queryText)
225
- ),
233
+ wrapCard("Query " + query.table_name, pre(queryText)),
226
234
  "Copilot"
227
235
  )
228
236
  );
@@ -240,25 +248,40 @@ const run = async (table_id, viewname, config, state, { res, req }) => {
240
248
  );
241
249
  break;
242
250
  case "tool":
243
- if (interact.name.startsWith("Query")) {
244
- const table = Table.findOne({
245
- name: interact.name.replace("Query", ""),
246
- });
247
- interactMarkups.push(
248
- await renderQueryInteraction(
249
- table,
250
- JSON.parse(interact.content),
251
- config,
252
- req
251
+ if (interact.name === "TableQuery") {
252
+ const tool_call = run.context.interactions
253
+ .map(
254
+ (i) =>
255
+ i.tool_calls &&
256
+ i.tool_calls.find((tc) => tc.id === interact.tool_call_id)
253
257
  )
254
- );
258
+ .filter(Boolean)[0];
259
+ if (tool_call) {
260
+ const args = JSON.parse(tool_call.function.arguments);
261
+ const table = Table.findOne(args.table_name);
262
+ interactMarkups.push(
263
+ await renderQueryInteraction(
264
+ table,
265
+ JSON.parse(interact.content),
266
+ config,
267
+ req
268
+ )
269
+ );
270
+ }
255
271
  } else if (interact.content !== "Action run") {
272
+ let markupContent;
273
+ try {
274
+ markupContent = JSON.stringify(
275
+ JSON.parse(interact.content),
276
+ null,
277
+ 2
278
+ );
279
+ } catch {
280
+ markupContent = interact.content;
281
+ }
256
282
  interactMarkups.push(
257
283
  wrapSegment(
258
- wrapCard(
259
- interact.name,
260
- pre(JSON.stringify(JSON.parse(interact.content), null, 2))
261
- ),
284
+ wrapCard(interact.name, pre(markupContent)),
262
285
  "Copilot"
263
286
  )
264
287
  );
@@ -443,32 +466,40 @@ const run = async (table_id, viewname, config, state, { res, req }) => {
443
466
  const getCompletionArguments = async (config) => {
444
467
  let tools = [];
445
468
  const sysPrompts = [];
446
- for (const tableCfg of config?.tables || []) {
447
- let properties = {};
469
+ let properties = {};
448
470
 
449
- const table = Table.findOne({ name: tableCfg.table_name });
471
+ const tableNames = (config?.tables || []).map((t) => t.table_name);
472
+ properties.table_name = {
473
+ type: "string",
474
+ enum: tableNames,
475
+ description: `Which table is this query from. Every query has to select rows from one table, even if it is based on joins from different tables`,
476
+ };
477
+ properties.sql_id_query = {
478
+ type: "string",
479
+ description: `An SQL query for this table's primary keys. This must select only the primary keys (even if the user wants a count), for example SELECT ${
480
+ tableNames[0][0]
481
+ }."${Table.findOne(tableNames[0]).pk_name}" from "${tableNames[0]}" ${
482
+ tableNames[0][0]
483
+ } JOIN ... where... Use this to join other tables in the database.`,
484
+ };
485
+ properties.is_count = {
486
+ type: "boolean",
487
+ description: `Is the only desired output a count? Make this true if the user wants a count of rows`,
488
+ };
450
489
 
451
- properties.sql_id_query = {
452
- type: "string",
453
- description: `An SQL query for this table's primary keys (${table.pk_name}). This must select only the primary keys (even if the user wants a count), for example SELECT ${table.name[0]}."${table.pk_name}" from "${table.name}" ${table.name[0]} JOIN ... where... Use this to join other tables in the database.`,
454
- };
455
- properties.is_count = {
456
- type: "boolean",
457
- description: `Is the only desired output a count? Make this true if the user wants a count of rows`,
458
- };
459
- tools.push({
460
- type: "function",
461
- function: {
462
- name: "Query" + table.name,
463
- description: `Query the ${table.name} table and show the results to the user in a grid format`,
464
- parameters: {
465
- type: "object",
466
- //required: ["action_javascript_code", "action_name"],
467
- properties,
468
- },
490
+ tools.push({
491
+ type: "function",
492
+ function: {
493
+ name: "TableQuery",
494
+ description: `Query a table and show the results to the user in a grid format`,
495
+ parameters: {
496
+ type: "object",
497
+ required: ["table_name", "sql_id_query", "is_count"],
498
+ properties,
469
499
  },
470
- });
471
- }
500
+ },
501
+ });
502
+
472
503
  for (const action of config?.actions || []) {
473
504
  let properties = {};
474
505
 
@@ -532,9 +563,10 @@ ${t.fields
532
563
  .join(";\n\n") +
533
564
  `
534
565
 
535
- If the user asks to you show rows from a table, just run the query tool for that table. This will display the result to the user.
536
- You should not render the results again, that will cause them to be displayed twice. Only if the user asks for a summary
537
- or a calculation based on the query results should you show that requested summary or calculation.
566
+ Use the TableQuery tool if the user asks to see, find or count or otherwise access rows from a table that matches what the user is looking for, or if
567
+ the user is asking for a summary or inference from such rows. The TableQuery query is parametrised by a SQL SELECT query which
568
+ selects primary key values from the specified table. You can join other tables or use complex logic in the WHERE clause, but you must
569
+ always return porimary key values from the specified table.
538
570
  `;
539
571
  //console.log("sysprompt", systemPrompt);
540
572
 
@@ -575,7 +607,7 @@ const renderQueryInteraction = async (table, result, config, req) => {
575
607
  if (typeof result === "number")
576
608
  return wrapSegment(
577
609
  wrapCard(
578
- "Query " + table.name,
610
+ "Query " + table?.name || "",
579
611
  //div("Query: ", code(JSON.stringify(query))),
580
612
  `${result}`
581
613
  ),
@@ -584,7 +616,7 @@ const renderQueryInteraction = async (table, result, config, req) => {
584
616
  if (result.length === 0)
585
617
  return wrapSegment(
586
618
  wrapCard(
587
- "Query " + table.name,
619
+ "Query " + table?.name || "",
588
620
  //div("Query: ", code(JSON.stringify(query))),
589
621
  "No rows found"
590
622
  ),
@@ -630,6 +662,7 @@ const process_interaction = async (
630
662
  ) => {
631
663
  const complArgs = await getCompletionArguments(config);
632
664
  complArgs.chat = run.context.interactions;
665
+ //complArgs.debugResult = true;
633
666
  //console.log(complArgs);
634
667
  console.log("complArgs", JSON.stringify(complArgs, null, 2));
635
668
 
@@ -638,12 +671,20 @@ const process_interaction = async (
638
671
  await addToContext(run, {
639
672
  interactions:
640
673
  typeof answer === "object" && answer.tool_calls
641
- ? [{ role: "assistant", tool_calls: answer.tool_calls }]
674
+ ? [
675
+ {
676
+ role: "assistant",
677
+ tool_calls: answer.tool_calls,
678
+ content: answer.content,
679
+ },
680
+ ]
642
681
  : [{ role: "assistant", content: answer }],
643
682
  });
644
683
  const responses = [];
645
684
 
646
685
  if (typeof answer === "object" && answer.tool_calls) {
686
+ if (answer.content)
687
+ responses.push(wrapSegment(md.render(answer.content), "Copilot"));
647
688
  //const actions = [];
648
689
  let hasResult = false;
649
690
  for (const tool_call of answer.tool_calls) {
@@ -702,12 +743,12 @@ const process_interaction = async (
702
743
  },
703
744
  ],
704
745
  });
705
- } else if (tool_call.function.name.startsWith("Query")) {
746
+ } else if (tool_call.function.name == "TableQuery") {
747
+ const query = JSON.parse(tool_call.function.arguments);
706
748
  const table = Table.findOne({
707
- name: tool_call.function.name.replace("Query", ""),
749
+ name: query.table_name,
708
750
  });
709
751
  const tableCfg = config.tables.find((t) => t.table_name === table.name);
710
- const query = JSON.parse(tool_call.function.arguments);
711
752
 
712
753
  const is_sqlite = db.isSQLite;
713
754