@saltcorn/agents 0.3.3 → 0.3.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/agent-view.js CHANGED
@@ -190,7 +190,7 @@ const run = async (
190
190
 
191
191
  for (const tool_call of interact.tool_calls || []) {
192
192
  const toolSkill = find_tool(
193
- tool_call.function.name,
193
+ tool_call.function?.name,
194
194
  action.configuration
195
195
  );
196
196
  if (toolSkill) {
package/common.js CHANGED
@@ -42,7 +42,7 @@ const find_tool = (name, config) => {
42
42
  : Array.isArray(skillTools)
43
43
  ? skillTools
44
44
  : [skillTools];
45
- const found = tools.find((t) => t?.function.name === name);
45
+ const found = tools.find((t) => t?.function?.name === name);
46
46
  if (found) return { tool: found, skill };
47
47
  }
48
48
  };
@@ -139,6 +139,23 @@ const wrapCard = (title, ...inners) =>
139
139
  div({ class: "card-body" }, inners)
140
140
  );
141
141
 
142
+ const only_response_text_if_present = (interact) => {
143
+ if (
144
+ interact.role === "tool" &&
145
+ interact.call_id &&
146
+ interact.content?.[0] === "{"
147
+ ) {
148
+ try {
149
+ const result = JSON.parse(interact.content);
150
+ if (result.responseText)
151
+ return { ...interact, content: result.responseText };
152
+ } catch {
153
+ //ignore, not json content
154
+ }
155
+ }
156
+ return interact;
157
+ };
158
+
142
159
  const process_interaction = async (
143
160
  run,
144
161
  config,
@@ -147,7 +164,7 @@ const process_interaction = async (
147
164
  prevResponses = []
148
165
  ) => {
149
166
  const complArgs = await getCompletionArguments(config, req.user);
150
- complArgs.chat = run.context.interactions;
167
+ complArgs.chat = run.context.interactions.map(only_response_text_if_present);
151
168
  //complArgs.debugResult = true;
152
169
  //console.log("complArgs", JSON.stringify(complArgs, null, 2));
153
170
 
@@ -198,7 +215,11 @@ const process_interaction = async (
198
215
  ]
199
216
  : [{ role: "assistant", content: answer }],
200
217
  });
201
- if (answer && typeof answer === "object" && (answer.tool_calls || answer.mcp_calls)) {
218
+ if (
219
+ answer &&
220
+ typeof answer === "object" &&
221
+ (answer.tool_calls || answer.mcp_calls)
222
+ ) {
202
223
  if (answer.content)
203
224
  responses.push(wrapSegment(md.render(answer.content), agent_label));
204
225
  //const actions = [];
@@ -211,7 +232,7 @@ const process_interaction = async (
211
232
  funcalls: { [tool_call.id]: tool_call.function },
212
233
  });
213
234
 
214
- const tool = find_tool(tool_call.function.name, config);
235
+ const tool = find_tool(tool_call.function?.name, config);
215
236
 
216
237
  if (tool) {
217
238
  if (tool.tool.renderToolCall) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/agents",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "AI agents for Saltcorn",
5
5
  "main": "index.js",
6
6
  "dependencies": {
@@ -5,6 +5,7 @@ const Table = require("@saltcorn/data/models/table");
5
5
  const View = require("@saltcorn/data/models/view");
6
6
  const { getState } = require("@saltcorn/data/db/state");
7
7
  const db = require("@saltcorn/data/db");
8
+ const { interpolate } = require("@saltcorn/data/utils");
8
9
 
9
10
  class RetrievalByEmbedding {
10
11
  static skill_name = "Retrieval by embedding";
@@ -109,6 +110,14 @@ class RetrievalByEmbedding {
109
110
  type: "String",
110
111
  fieldview: "textarea",
111
112
  },
113
+ {
114
+ name: "doc_format",
115
+ label: "Document format",
116
+ type: "String",
117
+ fieldview: "textarea",
118
+ sublabel:
119
+ "Format of text to send to LLM, use <code>{{ }}</code> to access variables in the document table. If not set, document will be sent as JSON",
120
+ },
112
121
  ];
113
122
  }
114
123
 
@@ -124,7 +133,7 @@ class RetrievalByEmbedding {
124
133
  : table0;
125
134
  return {
126
135
  type: "function",
127
- process: async ({ phrase_or_question }) => {
136
+ process: async ({ phrase_or_question }, { req }) => {
128
137
  const [table_name, field_name] = this.vec_field.split(".");
129
138
  const table = Table.findOne({ name: table_name });
130
139
  if (!table)
@@ -149,12 +158,20 @@ class RetrievalByEmbedding {
149
158
  rows.forEach((r) => {
150
159
  delete r[field_name];
151
160
  });
161
+
162
+ const set_doc_format = (rows) => {
163
+ if (!this.doc_format) return { rows };
164
+ const responseText = rows
165
+ .map((row) => interpolate(this.doc_format, row, req.user))
166
+ .join("\n");
167
+ return { rows, responseText };
168
+ };
152
169
  if (!rows.length)
153
170
  return {
154
- response:
171
+ responseText:
155
172
  "There are no documents related to: " + phrase_or_question,
156
173
  };
157
- else if (!this.doc_relation) return { rows };
174
+ else if (!this.doc_relation) return set_doc_format(rows);
158
175
  else {
159
176
  const relField = table.getField(this.doc_relation);
160
177
  const relTable = Table.findOne(relField.reftable_name);
@@ -167,10 +184,10 @@ class RetrievalByEmbedding {
167
184
  const docs = ids
168
185
  .map((id) => docsUnsorted.find((d) => d.id == id))
169
186
  .filter(Boolean);
170
- return { rows: docs };
187
+ return set_doc_format(docs);
171
188
  }
172
189
  },
173
- renderToolResponse: async ({ response, rows }, { req }) => {
190
+ renderToolResponse: async ({ responseText, rows }, { req }) => {
174
191
  if (rows) {
175
192
  const view = View.findOne({ name: this.list_view });
176
193
 
@@ -186,7 +203,7 @@ class RetrievalByEmbedding {
186
203
  return viewRes;
187
204
  } else return "";
188
205
  }
189
- return div({ class: "border border-success p-2 m-2" }, response);
206
+ return div(responseText);
190
207
  },
191
208
  function: {
192
209
  name: this.toolName,
@@ -5,6 +5,7 @@ const Table = require("@saltcorn/data/models/table");
5
5
  const View = require("@saltcorn/data/models/view");
6
6
  const { getState } = require("@saltcorn/data/db/state");
7
7
  const db = require("@saltcorn/data/db");
8
+ const { interpolate } = require("@saltcorn/data/utils");
8
9
 
9
10
  class RetrievalByFullTextSearch {
10
11
  static skill_name = "Retrieval by full-text search";
@@ -84,13 +85,14 @@ class RetrievalByFullTextSearch {
84
85
  type: "String",
85
86
  fieldview: "textarea",
86
87
  },
87
- /*{
88
- name: "contents_expr",
89
- label: "Contents string",
88
+ {
89
+ name: "doc_format",
90
+ label: "Document format",
90
91
  type: "String",
92
+ fieldview: "textarea",
91
93
  sublabel:
92
- "Use handlebars (<code>{{ }}</code>) to access fields in the retrieved rows",
93
- },*/
94
+ "Format of text to send to LLM, use <code>{{ }}</code> to access variables in the document table. If not set, document will be sent as JSON",
95
+ },
94
96
  ];
95
97
  }
96
98
 
@@ -103,7 +105,7 @@ class RetrievalByFullTextSearch {
103
105
  const table = Table.findOne(this.table_name);
104
106
  return {
105
107
  type: "function",
106
- process: async ({ phrase }) => {
108
+ process: async ({ phrase }, { req }) => {
107
109
  const scState = getState();
108
110
  const language = scState.pg_ts_config;
109
111
  const use_websearch = scState.getConfig("search_use_websearch", false);
@@ -127,16 +129,23 @@ class RetrievalByFullTextSearch {
127
129
  });
128
130
  });
129
131
  }
130
- if (rows.length) return { rows };
132
+ if (rows.length)
133
+ if (!this.doc_format) return { rows };
134
+ else {
135
+ const responseText = rows
136
+ .map((row) => interpolate(this.doc_format, row, req.user))
137
+ .join("\n");
138
+ return { rows, responseText };
139
+ }
131
140
  else
132
141
  return {
133
- response: "There are no rows related to: " + phrase,
142
+ responseText: "There are no rows related to: " + phrase,
134
143
  };
135
144
  },
136
145
  /*renderToolCall({ phrase }, { req }) {
137
146
  return div({ class: "border border-primary p-2 m-2" }, phrase);
138
147
  },*/
139
- renderToolResponse: async ({ response, rows }, { req }) => {
148
+ renderToolResponse: async ({ responseText, rows }, { req }) => {
140
149
  if (rows) {
141
150
  const view = View.findOne({ name: this.list_view });
142
151
 
@@ -148,7 +157,7 @@ class RetrievalByFullTextSearch {
148
157
  return viewRes;
149
158
  } else return "";
150
159
  }
151
- return div({ class: "border border-success p-2 m-2" }, response);
160
+ return div({ class: "border border-success p-2 m-2" }, responseText);
152
161
  },
153
162
  function: {
154
163
  name: this.toolName,
@@ -161,7 +170,8 @@ class RetrievalByFullTextSearch {
161
170
  properties: {
162
171
  phrase: {
163
172
  type: "string",
164
- description: "The phrase to search the table with. The search phrase is the synatx used by web search engines: use double quotes for exact match, unquoted text for words in any order, dash (minus sign) to exclude a word. Do not use SQL or any other formal query language.",
173
+ description:
174
+ "The phrase to search the table with. The search phrase is the synatx used by web search engines: use double quotes for exact match, unquoted text for words in any order, dash (minus sign) to exclude a word. Do not use SQL or any other formal query language.",
165
175
  },
166
176
  },
167
177
  },