@aigne/core 0.4.203 → 0.4.205-0

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/lib/cjs/index.js CHANGED
@@ -27,4 +27,4 @@ __exportStar(require("./function-agent"), exports);
27
27
  __exportStar(require("./function-runner"), exports);
28
28
  __exportStar(require("./llm-decision-agent"), exports);
29
29
  __exportStar(require("./local-function-agent"), exports);
30
- __exportStar(require("./memory"), exports);
30
+ __exportStar(require("./memorable"), exports);
@@ -11,83 +11,73 @@ var __metadata = (this && this.__metadata) || function (k, v) {
11
11
  var __param = (this && this.__param) || function (paramIndex, decorator) {
12
12
  return function (target, key) { decorator(target, key, paramIndex); }
13
13
  };
14
+ var __importDefault = (this && this.__importDefault) || function (mod) {
15
+ return (mod && mod.__esModule) ? mod : { "default": mod };
16
+ };
14
17
  var LLMAgent_1;
15
18
  Object.defineProperty(exports, "__esModule", { value: true });
16
19
  exports.LLMAgent = void 0;
17
20
  exports.createLLMAgentDefinition = createLLMAgentDefinition;
18
- const lodash_1 = require("lodash");
21
+ const remove_1 = __importDefault(require("lodash/remove"));
19
22
  const nanoid_1 = require("nanoid");
20
23
  const tsyringe_1 = require("tsyringe");
21
24
  const constants_1 = require("./constants");
22
25
  const data_type_schema_1 = require("./data-type-schema");
23
26
  const llm_model_1 = require("./llm-model");
27
+ const logger_1 = __importDefault(require("./logger"));
24
28
  const runnable_1 = require("./runnable");
25
29
  const utils_1 = require("./utils");
30
+ const message_utils_1 = require("./utils/message-utils");
26
31
  const mustache_utils_1 = require("./utils/mustache-utils");
27
32
  const ordered_map_1 = require("./utils/ordered-map");
33
+ const structured_output_schema_1 = require("./utils/structured-output-schema");
28
34
  let LLMAgent = LLMAgent_1 = class LLMAgent extends runnable_1.Runnable {
29
35
  definition;
30
36
  model;
37
+ context;
31
38
  static create(options) {
32
39
  const definition = createLLMAgentDefinition(options);
33
40
  return new LLMAgent_1(definition);
34
41
  }
35
- constructor(definition, model) {
42
+ constructor(definition, model, context) {
36
43
  super(definition);
37
44
  this.definition = definition;
38
45
  this.model = model;
46
+ this.context = context;
39
47
  }
40
48
  async run(input, options) {
41
49
  const { definition, model } = this;
42
50
  if (!model)
43
51
  throw new Error('LLM model is required');
44
- const messages = ordered_map_1.OrderedRecord.toArray(definition.messages);
45
- if (!messages.length)
46
- throw new Error('Messages are required');
47
- // TODO: support comment/image for messages
52
+ const { originalMessages, messagesWithMemory } = await this.prepareMessages(input);
48
53
  const llmInputs = {
49
- messages: messages.map(({ role, content }) => ({
50
- role,
51
- content: (0, mustache_utils_1.renderMessage)(content, input),
52
- })),
54
+ messages: messagesWithMemory,
53
55
  modelOptions: definition.modelOptions,
54
56
  };
55
- const outputs = ordered_map_1.OrderedRecord.toArray(definition.outputs).filter((0, utils_1.isPropsNonNullable)('name'));
56
- const textOutput = outputs.find((i) => i.name === constants_1.StreamTextOutputName);
57
- const jsonOutputs = outputs.filter((i) => i.name !== constants_1.StreamTextOutputName);
58
- const outputJsonSchema = jsonOutputs.length ? outputsToJsonSchema(ordered_map_1.OrderedRecord.fromArray(jsonOutputs)) : undefined;
59
- const jsonOutput = outputJsonSchema
60
- ? model
61
- .run({
62
- ...llmInputs,
63
- responseFormat: outputJsonSchema && {
64
- type: 'json_schema',
65
- jsonSchema: {
66
- name: 'output',
67
- schema: outputJsonSchema,
68
- strict: true,
69
- },
70
- },
71
- })
72
- .then(async (response) => {
73
- if (!response.$text)
74
- throw new Error('No text in JSON mode response');
75
- const json = JSON.parse(response.$text);
76
- // TODO: validate json with outputJsonSchema
77
- return json;
78
- })
57
+ const jsonOutput = this.runWithStructuredOutput(llmInputs);
58
+ const textOutput = ordered_map_1.OrderedRecord.find(definition.outputs, (i) => i.name === constants_1.StreamTextOutputName)
59
+ ? await this.runWithTextOutput(llmInputs)
79
60
  : undefined;
61
+ const updateMemories = (text, json) => {
62
+ return this.updateMemories([
63
+ ...originalMessages,
64
+ { role: 'assistant', content: (0, mustache_utils_1.renderMessage)('{{text}}\n{{json}}', { text, json }).trim() },
65
+ ]);
66
+ };
80
67
  if (options?.stream) {
68
+ let $text = '';
81
69
  return new ReadableStream({
82
70
  start: async (controller) => {
83
71
  try {
84
72
  if (textOutput) {
85
- const textStreamOutput = await model.run(llmInputs, { stream: true });
86
- for await (const chunk of textStreamOutput) {
73
+ for await (const chunk of textOutput) {
74
+ $text += chunk.$text || '';
87
75
  controller.enqueue({ $text: chunk.$text });
88
76
  }
89
77
  }
90
- controller.enqueue({ delta: await jsonOutput });
78
+ const json = await jsonOutput;
79
+ controller.enqueue({ delta: json });
80
+ await updateMemories($text || undefined, json);
91
81
  }
92
82
  catch (error) {
93
83
  controller.error(error);
@@ -98,62 +88,185 @@ let LLMAgent = LLMAgent_1 = class LLMAgent extends runnable_1.Runnable {
98
88
  },
99
89
  });
100
90
  }
101
- const text = textOutput ? await model.run(llmInputs) : undefined;
91
+ const [$text, json] = await Promise.all([
92
+ textOutput ? (0, utils_1.runnableResponseStreamToObject)(textOutput).then((res) => res.$text || undefined) : undefined,
93
+ jsonOutput,
94
+ ]);
95
+ await updateMemories($text, json);
96
+ return { $text, ...json };
97
+ }
98
+ async prepareMessages(input) {
99
+ const { definition } = this;
100
+ const originalMessages = ordered_map_1.OrderedRecord.toArray(definition.messages).map(({ role, content }) => ({
101
+ role,
102
+ // TODO: support use memory variables in message content
103
+ content: typeof content === 'string' ? (0, mustache_utils_1.renderMessage)(content, input) : content,
104
+ }));
105
+ if (!originalMessages.length)
106
+ throw new Error('Messages are required');
107
+ const { primaryMemory, memory } = await this.getMemories(input);
108
+ let messagesWithMemory = [...originalMessages];
109
+ // Add memory to a system message
110
+ if (memory) {
111
+ const message = {
112
+ role: 'system',
113
+ content: `\
114
+ Here are the memories about the user:
115
+ ${memory}
116
+ `,
117
+ };
118
+ const lastSystemMessageIndex = messagesWithMemory.findLastIndex((i) => i.role === 'assistant');
119
+ messagesWithMemory.splice(lastSystemMessageIndex + 1, 0, message);
120
+ }
121
+ // Add primary memory to messages
122
+ if (primaryMemory.length)
123
+ messagesWithMemory = (0, message_utils_1.mergeHistoryMessages)(messagesWithMemory, primaryMemory);
124
+ // TODO: support comment/image for messages
125
+ return { originalMessages, messagesWithMemory };
126
+ }
127
+ async runWithStructuredOutput(llmInputs) {
128
+ const jsonOutputs = ordered_map_1.OrderedRecord.filter(this.definition.outputs, (i) => i.name !== constants_1.StreamTextOutputName // ignore `$text` output
129
+ );
130
+ if (!jsonOutputs.length)
131
+ return null;
132
+ const schema = (0, structured_output_schema_1.outputsToJsonSchema)(ordered_map_1.OrderedRecord.fromArray(jsonOutputs));
133
+ const { model } = this;
134
+ if (!model)
135
+ throw new Error('LLM model is required');
136
+ const response = await model.run({
137
+ ...llmInputs,
138
+ responseFormat: {
139
+ type: 'json_schema',
140
+ jsonSchema: {
141
+ name: 'output',
142
+ schema: schema,
143
+ strict: true,
144
+ },
145
+ },
146
+ });
147
+ if (!response.$text)
148
+ throw new Error('No text in JSON mode response');
149
+ const json = JSON.parse(response.$text);
150
+ // TODO: validate json with outputJsonSchema
151
+ return json;
152
+ }
153
+ async runWithTextOutput(llmInputs) {
154
+ const { model } = this;
155
+ if (!model)
156
+ throw new Error('LLM model is required');
157
+ return model.run(llmInputs, { stream: true });
158
+ }
159
+ async getMemoryQuery(input, query) {
160
+ if (query?.from === 'variable') {
161
+ const i = ordered_map_1.OrderedRecord.find(this.definition.inputs, (i) => i.id === query.fromVariableId);
162
+ if (!i)
163
+ throw new Error(`Input variable ${query.fromVariableId} not found`);
164
+ const value = input[i.name];
165
+ return (0, mustache_utils_1.renderMessage)('{{value}}', { value });
166
+ }
167
+ return Object.entries(input)
168
+ .map(([key, value]) => `${key} ${value}`)
169
+ .join('\n');
170
+ }
171
+ async getMemories(input) {
172
+ const { memories } = this.definition;
173
+ const { userId, sessionId } = this.context?.state ?? {};
174
+ const list = (await Promise.all(ordered_map_1.OrderedRecord.map(memories, async ({ id, memory, query, options }) => {
175
+ if (!memory) {
176
+ logger_1.default.warn(`Memory is not defined in agent ${this.name || this.id}`);
177
+ return null;
178
+ }
179
+ const q = await this.getMemoryQuery(input, query);
180
+ const { results: memories } = await memory.search(q, { ...options, userId, sessionId });
181
+ return { id, memories };
182
+ }))).filter(utils_1.isNonNullable);
183
+ const primary = (0, remove_1.default)(list, (i) => i.id === this.definition.primaryMemoryId)[0]?.memories || [];
184
+ const primaryMemory = primary
185
+ .map((i) => {
186
+ const content = (0, mustache_utils_1.renderMessage)('{{memory}}', { memory: i.memory }).trim();
187
+ const role = ['user', 'assistant'].includes(i.metadata.role) ? i.metadata.role : undefined;
188
+ if (!role || !content)
189
+ return null;
190
+ return { role, content };
191
+ })
192
+ .filter(utils_1.isNonNullable);
193
+ const memory = list
194
+ .map((i) => i.memories
195
+ .map((j) => (0, mustache_utils_1.renderMessage)('{{memory}}\n{{metadata}}', j).trim() || null)
196
+ .filter(utils_1.isNonNullable)
197
+ .join('\n'))
198
+ .join('\n');
102
199
  return {
103
- $text: text?.$text,
104
- ...(await jsonOutput),
200
+ primaryMemory,
201
+ memory,
105
202
  };
106
203
  }
204
+ /**
205
+ * Update memories by user messages and assistant responses.
206
+ * @param messages Messages to be added to memories.
207
+ */
208
+ async updateMemories(messages) {
209
+ const { memories } = this.definition;
210
+ const { userId, sessionId } = this.context?.state ?? {};
211
+ await Promise.all(ordered_map_1.OrderedRecord.map(memories, async ({ memory }) => {
212
+ if (!memory) {
213
+ logger_1.default.warn(`Memory is not defined in agent ${this.name || this.id}`);
214
+ return;
215
+ }
216
+ return await memory.add(messages, { userId, sessionId });
217
+ }));
218
+ }
107
219
  };
108
220
  exports.LLMAgent = LLMAgent;
109
221
  exports.LLMAgent = LLMAgent = LLMAgent_1 = __decorate([
110
222
  (0, tsyringe_1.injectable)(),
111
223
  __param(0, (0, tsyringe_1.inject)(constants_1.TYPES.definition)),
112
224
  __param(1, (0, tsyringe_1.inject)(constants_1.TYPES.llmModel)),
113
- __metadata("design:paramtypes", [Object, llm_model_1.LLMModel])
225
+ __param(2, (0, tsyringe_1.inject)(constants_1.TYPES.context)),
226
+ __metadata("design:paramtypes", [Object, llm_model_1.LLMModel, Object])
114
227
  ], LLMAgent);
228
+ /**
229
+ * Create LLMAgent definition.
230
+ * @param options Options to create LLMAgent.
231
+ * @returns LLMAgent definition.
232
+ */
115
233
  function createLLMAgentDefinition(options) {
234
+ const agentId = options.name || (0, nanoid_1.nanoid)();
235
+ const primaryMemories = options.memories?.filter((i) => i.primary);
236
+ if (primaryMemories && primaryMemories.length > 1) {
237
+ throw new Error('Only one primary memory is allowed');
238
+ }
239
+ const inputs = (0, data_type_schema_1.schemaToDataType)(options.inputs);
240
+ const outputs = (0, data_type_schema_1.schemaToDataType)(options.outputs);
241
+ const memories = ordered_map_1.OrderedRecord.fromArray(options.memories?.map((i) => {
242
+ const { name, memory, query, options } = i;
243
+ const queryFromVariable = query?.fromVariable
244
+ ? ordered_map_1.OrderedRecord.find(inputs, (j) => j.name === query.fromVariable)
245
+ : null;
246
+ if (query?.fromVariable && !queryFromVariable)
247
+ throw new Error(`LLMAgent ${agentId} -> Memory ${name} -> Query variable ${query.fromVariable.toString()} not found`);
248
+ return {
249
+ id: name || (0, nanoid_1.nanoid)(),
250
+ name: name,
251
+ memory: memory,
252
+ query: queryFromVariable ? { from: 'variable', fromVariableId: queryFromVariable.id } : undefined,
253
+ options,
254
+ };
255
+ }));
256
+ const messages = ordered_map_1.OrderedRecord.fromArray(options.messages?.map((i) => ({
257
+ id: (0, nanoid_1.nanoid)(),
258
+ role: i.role,
259
+ content: i.content,
260
+ })));
116
261
  return {
117
- id: options.id || options.name || (0, nanoid_1.nanoid)(),
262
+ id: agentId,
118
263
  name: options.name,
119
264
  type: 'llm_agent',
120
- inputs: (0, data_type_schema_1.schemaToDataType)(options.inputs),
121
- outputs: (0, data_type_schema_1.schemaToDataType)(options.outputs),
265
+ inputs,
266
+ outputs,
267
+ primaryMemoryId: primaryMemories?.at(0)?.name,
268
+ memories,
122
269
  modelOptions: options.modelOptions,
123
- messages: ordered_map_1.OrderedRecord.fromArray(options.messages?.map((i) => ({
124
- id: (0, nanoid_1.nanoid)(),
125
- role: i.role,
126
- content: i.content,
127
- }))),
128
- };
129
- }
130
- function outputsToJsonSchema(outputs) {
131
- const outputToSchema = (output) => {
132
- const properties = output.type === 'object' && output.properties?.$indexes.length
133
- ? ordered_map_1.OrderedRecord.map(output.properties, (property) => {
134
- if (!property.name)
135
- return null;
136
- const schema = outputToSchema(property);
137
- if (!schema)
138
- return null;
139
- return { schema, property };
140
- }).filter(utils_1.isNonNullable)
141
- : undefined;
142
- return (0, lodash_1.omitBy)({
143
- type: output.type,
144
- description: output.description,
145
- properties: properties?.length
146
- ? Object.fromEntries(properties.map((p) => [p.property.name, p.schema]))
147
- : undefined,
148
- items: output.type === 'array' && output.items ? outputToSchema(output.items) : undefined,
149
- additionalProperties: output.type === 'object' ? false : undefined,
150
- required: properties?.length
151
- ? properties.filter((i) => i.property.required).map((i) => i.property.name)
152
- : undefined,
153
- }, (v) => v === undefined);
270
+ messages,
154
271
  };
155
- return outputToSchema({
156
- type: 'object',
157
- properties: outputs,
158
- });
159
272
  }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemoryRunner = exports.Memorable = void 0;
4
+ const lodash_1 = require("lodash");
5
+ const runnable_1 = require("./runnable");
6
+ const utils_1 = require("./utils");
7
+ class Memorable extends runnable_1.Runnable {
8
+ constructor() {
9
+ super({
10
+ id: 'memory',
11
+ type: 'memory',
12
+ name: 'Memory',
13
+ inputs: utils_1.OrderedRecord.fromArray([]),
14
+ outputs: utils_1.OrderedRecord.fromArray([]),
15
+ });
16
+ }
17
+ }
18
+ exports.Memorable = Memorable;
19
+ class MemoryRunner extends runnable_1.Runnable {
20
+ constructor(name) {
21
+ const id = `${(0, lodash_1.camelCase)(name)}_runner`;
22
+ super({
23
+ id,
24
+ type: id,
25
+ name: `${(0, lodash_1.startCase)(name)} Runner`,
26
+ description: `${(0, lodash_1.startCase)(name)} Runner`,
27
+ inputs: utils_1.OrderedRecord.fromArray([]),
28
+ outputs: utils_1.OrderedRecord.fromArray([]),
29
+ });
30
+ }
31
+ }
32
+ exports.MemoryRunner = MemoryRunner;