@falai/agent 1.1.1 → 1.1.2

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 (50) hide show
  1. package/dist/cjs/core/Agent.d.ts +202 -67
  2. package/dist/cjs/core/Agent.d.ts.map +1 -1
  3. package/dist/cjs/core/Agent.js +366 -158
  4. package/dist/cjs/core/Agent.js.map +1 -1
  5. package/dist/cjs/core/BatchExecutor.d.ts.map +1 -1
  6. package/dist/cjs/core/BatchExecutor.js +4 -2
  7. package/dist/cjs/core/BatchExecutor.js.map +1 -1
  8. package/dist/cjs/core/BatchPromptBuilder.d.ts.map +1 -1
  9. package/dist/cjs/core/BatchPromptBuilder.js +5 -2
  10. package/dist/cjs/core/BatchPromptBuilder.js.map +1 -1
  11. package/dist/cjs/core/ResponseEngine.d.ts.map +1 -1
  12. package/dist/cjs/core/ResponseEngine.js +6 -3
  13. package/dist/cjs/core/ResponseEngine.js.map +1 -1
  14. package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
  15. package/dist/cjs/core/ResponseModal.js +18 -17
  16. package/dist/cjs/core/ResponseModal.js.map +1 -1
  17. package/dist/cjs/core/RoutingEngine.d.ts.map +1 -1
  18. package/dist/cjs/core/RoutingEngine.js +8 -73
  19. package/dist/cjs/core/RoutingEngine.js.map +1 -1
  20. package/dist/core/Agent.d.ts +202 -67
  21. package/dist/core/Agent.d.ts.map +1 -1
  22. package/dist/core/Agent.js +366 -158
  23. package/dist/core/Agent.js.map +1 -1
  24. package/dist/core/BatchExecutor.d.ts.map +1 -1
  25. package/dist/core/BatchExecutor.js +4 -2
  26. package/dist/core/BatchExecutor.js.map +1 -1
  27. package/dist/core/BatchPromptBuilder.d.ts.map +1 -1
  28. package/dist/core/BatchPromptBuilder.js +5 -2
  29. package/dist/core/BatchPromptBuilder.js.map +1 -1
  30. package/dist/core/ResponseEngine.d.ts.map +1 -1
  31. package/dist/core/ResponseEngine.js +6 -3
  32. package/dist/core/ResponseEngine.js.map +1 -1
  33. package/dist/core/ResponseModal.d.ts.map +1 -1
  34. package/dist/core/ResponseModal.js +18 -17
  35. package/dist/core/ResponseModal.js.map +1 -1
  36. package/dist/core/RoutingEngine.d.ts.map +1 -1
  37. package/dist/core/RoutingEngine.js +8 -73
  38. package/dist/core/RoutingEngine.js.map +1 -1
  39. package/docs/api/README.md +2 -2
  40. package/docs/api/overview.md +1 -1
  41. package/docs/architecture/data-extraction-flow.md +17 -19
  42. package/docs/core/conversation-flows/data-collection.md +2 -2
  43. package/docs/core/error-handling.md +3 -4
  44. package/package.json +2 -2
  45. package/src/core/Agent.ts +427 -195
  46. package/src/core/BatchExecutor.ts +5 -2
  47. package/src/core/BatchPromptBuilder.ts +41 -38
  48. package/src/core/ResponseEngine.ts +56 -53
  49. package/src/core/ResponseModal.ts +79 -81
  50. package/src/core/RoutingEngine.ts +67 -149
@@ -335,8 +335,11 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
335
335
  const warning = `[Agent] Step "${step.description || step.id}" requires data [${missingRequires.join(', ')}] that was not collected by previous steps. ` +
336
336
  `Ensure earlier steps collect these fields before this step can proceed.`;
337
337
  logger.warn(warning);
338
- // Also log to console for developer visibility
339
- console.warn(warning);
338
+
339
+ // Also log to console for developer visibility (but silence in test suites to prevent fast-check spam)
340
+ if (process.env.NODE_ENV !== 'test') {
341
+ console.warn(warning);
342
+ }
340
343
  }
341
344
 
342
345
  logger.debug(`[BatchExecutor] Step ${step.id} needs input, stopping batch. Missing requires: [${missingRequires.join(', ')}], Collect fields: [${collectFields.join(', ')}]`);
@@ -66,7 +66,7 @@ export class BatchPromptBuilder<TContext = unknown, TData = unknown> {
66
66
  */
67
67
  async buildBatchPrompt(params: BuildBatchPromptParams<TContext, TData>): Promise<BatchPromptResult> {
68
68
  const { steps, route, history, context, session, agentOptions } = params;
69
-
69
+
70
70
  // Create template context for rendering
71
71
  const templateContext = createTemplateContext<TContext, TData>({
72
72
  context,
@@ -74,7 +74,7 @@ export class BatchPromptBuilder<TContext = unknown, TData = unknown> {
74
74
  session,
75
75
  history,
76
76
  });
77
-
77
+
78
78
  // Collect all collect fields from all steps
79
79
  const collectFields: string[] = [];
80
80
  for (const step of steps) {
@@ -87,13 +87,13 @@ export class BatchPromptBuilder<TContext = unknown, TData = unknown> {
87
87
  }
88
88
  }
89
89
  }
90
-
90
+
91
91
  // Build the combined prompt using PromptComposer for consistency
92
92
  const composer = new PromptComposer<TContext, TData>(templateContext);
93
-
93
+
94
94
  // Add agent meta information
95
95
  await composer.addAgentMeta(agentOptions);
96
-
96
+
97
97
  // Add route-specific identity/personality if available
98
98
  if (route.identity) {
99
99
  const identity = await render(route.identity, templateContext);
@@ -101,25 +101,25 @@ export class BatchPromptBuilder<TContext = unknown, TData = unknown> {
101
101
  await composer.addInstruction(`**Route Identity:** ${identity}`);
102
102
  }
103
103
  }
104
-
104
+
105
105
  if (route.personality) {
106
106
  const personality = await render(route.personality, templateContext);
107
107
  if (personality) {
108
108
  await composer.addInstruction(`**Route Personality:** ${personality}`);
109
109
  }
110
110
  }
111
-
111
+
112
112
  // Add knowledge base if available
113
113
  await composer.addKnowledgeBase(agentOptions.knowledgeBase, route.getKnowledgeBase());
114
-
114
+
115
115
  // Add glossary terms
116
116
  const allTerms = [...(agentOptions.terms || []), ...route.getTerms()];
117
117
  await composer.addGlossary(allTerms);
118
-
118
+
119
119
  // Add guidelines
120
120
  const allGuidelines = [...(agentOptions.guidelines || []), ...route.getGuidelines()];
121
121
  await composer.addGuidelines(allGuidelines);
122
-
122
+
123
123
  // Add combined rules (agent + route)
124
124
  const allRules = [...(agentOptions.rules || []), ...route.getRules()];
125
125
  if (allRules.length > 0) {
@@ -128,7 +128,7 @@ export class BatchPromptBuilder<TContext = unknown, TData = unknown> {
128
128
  await composer.addInstruction(`Rules:\n- ${renderedRules.join('\n- ')}`);
129
129
  }
130
130
  }
131
-
131
+
132
132
  // Add combined prohibitions (agent + route)
133
133
  const allProhibitions = [...(agentOptions.prohibitions || []), ...route.getProhibitions()];
134
134
  if (allProhibitions.length > 0) {
@@ -137,13 +137,13 @@ export class BatchPromptBuilder<TContext = unknown, TData = unknown> {
137
137
  await composer.addInstruction(`Prohibitions:\n- ${renderedProhibitions.join('\n- ')}`);
138
138
  }
139
139
  }
140
-
140
+
141
141
  // Add interaction history
142
142
  await composer.addInteractionHistory(history, 'Recent conversation context:');
143
-
143
+
144
144
  // Build the step sections
145
145
  const stepSections = await this.buildStepSections(steps, templateContext);
146
-
146
+
147
147
  // Add the conversation flow section
148
148
  if (steps.length > 1) {
149
149
  await composer.addInstruction(
@@ -157,27 +157,27 @@ export class BatchPromptBuilder<TContext = unknown, TData = unknown> {
157
157
  stepSections
158
158
  );
159
159
  }
160
-
160
+
161
161
  // Add data collection section if there are fields to collect
162
162
  if (collectFields.length > 0) {
163
163
  const collectionSection = this.buildDataCollectionSection(collectFields, agentOptions);
164
164
  await composer.addInstruction(collectionSection);
165
165
  }
166
-
166
+
167
167
  // Add response format instructions
168
168
  const responseFormat = this.buildResponseFormatSection(collectFields);
169
169
  await composer.addInstruction(responseFormat);
170
-
170
+
171
171
  // Build the final prompt
172
172
  const prompt = await composer.build();
173
-
173
+
174
174
  return {
175
175
  prompt,
176
176
  collectFields,
177
177
  stepCount: steps.length,
178
178
  };
179
179
  }
180
-
180
+
181
181
  /**
182
182
  * Build the step sections of the prompt
183
183
  *
@@ -190,15 +190,15 @@ export class BatchPromptBuilder<TContext = unknown, TData = unknown> {
190
190
  templateContext: ReturnType<typeof createTemplateContext<TContext, TData>>
191
191
  ): Promise<string> {
192
192
  const sections: string[] = [];
193
-
193
+
194
194
  for (let i = 0; i < steps.length; i++) {
195
195
  const step = steps[i];
196
196
  const stepNumber = i + 1;
197
-
197
+
198
198
  // Build step header
199
199
  const description = step.description || `Step ${stepNumber}`;
200
200
  let section = `### Step ${stepNumber}: ${description}\n`;
201
-
201
+
202
202
  // Render and add step prompt if available
203
203
  if (step.prompt) {
204
204
  const renderedPrompt = await render(step.prompt, templateContext);
@@ -206,19 +206,19 @@ export class BatchPromptBuilder<TContext = unknown, TData = unknown> {
206
206
  section += `\n${renderedPrompt}\n`;
207
207
  }
208
208
  }
209
-
209
+
210
210
  // Add collect fields for this step if any
211
211
  if (step.collect && step.collect.length > 0) {
212
212
  const collectList = step.collect.map(f => `\`${String(f)}\``).join(', ');
213
213
  section += `\n**Collect:** ${collectList}\n`;
214
214
  }
215
-
215
+
216
216
  sections.push(section);
217
217
  }
218
-
218
+
219
219
  return sections.join('\n');
220
220
  }
221
-
221
+
222
222
  /**
223
223
  * Build the data collection section of the prompt
224
224
  *
@@ -236,36 +236,36 @@ export class BatchPromptBuilder<TContext = unknown, TData = unknown> {
236
236
  'Extract the following information from your response:',
237
237
  ''
238
238
  ];
239
-
239
+
240
240
  // Get schema information if available
241
241
  const schema = agentOptions.schema;
242
-
242
+
243
243
  for (const field of collectFields) {
244
244
  let fieldDescription = field;
245
-
245
+
246
246
  // Try to get field type/description from schema
247
247
  if (schema?.properties && schema.properties[field]) {
248
248
  const fieldSchema = schema.properties[field] as Record<string, unknown>;
249
249
  const rawType = fieldSchema.type;
250
250
  const rawDescription = fieldSchema.description;
251
-
251
+
252
252
  // Safely convert to string, handling objects and primitives
253
253
  const type = typeof rawType === 'string' ? rawType : 'string';
254
254
  const description = typeof rawDescription === 'string' ? rawDescription : '';
255
-
255
+
256
256
  if (description) {
257
257
  fieldDescription = `${field} (${type}): ${description}`;
258
258
  } else {
259
259
  fieldDescription = `${field} (${type})`;
260
260
  }
261
261
  }
262
-
262
+
263
263
  lines.push(`- ${fieldDescription}`);
264
264
  }
265
-
265
+
266
266
  return lines.join('\n');
267
267
  }
268
-
268
+
269
269
  /**
270
270
  * Build the response format section of the prompt
271
271
  *
@@ -277,17 +277,20 @@ export class BatchPromptBuilder<TContext = unknown, TData = unknown> {
277
277
  '## Response Format',
278
278
  '',
279
279
  'Return JSON with:',
280
- '- `message`: Your response to the user'
280
+ '- `message`: A natural, conversational response directed at the user. This MUST read like a human conversation.',
281
+ '',
282
+ 'CRITICAL: The `message` field must NEVER contain field names, JSON keys, raw data values, or technical information.',
283
+ 'Data goes in the separate JSON fields below, NOT in the message text.',
281
284
  ];
282
-
285
+
283
286
  if (collectFields.length > 0) {
284
287
  lines.push('');
285
- lines.push('Include the following collected fields as top-level properties:');
288
+ lines.push('Include the following collected fields as top-level properties (separate from message):');
286
289
  for (const field of collectFields) {
287
290
  lines.push(`- \`${field}\``);
288
291
  }
289
292
  }
290
-
293
+
291
294
  return lines.join('\n');
292
295
  }
293
296
  }
@@ -44,48 +44,48 @@ export interface BuildFallbackPromptParams<TContext = unknown, TData = unknown>
44
44
 
45
45
  export class ResponseEngine<TContext = unknown, TData = unknown> {
46
46
  responseSchemaForRoute(
47
- route: Route<TContext, TData>,
48
- currentStep?: Step<TContext, TData>,
49
- agentSchema?: StructuredSchema
50
- ): StructuredSchema {
51
- const base: StructuredSchema = {
52
- type: "object",
53
- properties: {
54
- message: { type: "string", description: "Final user-facing message" },
55
- },
56
- required: ["message"],
57
- additionalProperties: false,
58
- };
47
+ route: Route<TContext, TData>,
48
+ currentStep?: Step<TContext, TData>,
49
+ agentSchema?: StructuredSchema
50
+ ): StructuredSchema {
51
+ const base: StructuredSchema = {
52
+ type: "object",
53
+ properties: {
54
+ message: { type: "string", description: "Natural, conversational response directed at the user. Must NOT contain field names, raw data, or internal information." },
55
+ },
56
+ required: ["message"],
57
+ additionalProperties: false,
58
+ };
59
59
 
60
- // Add data field only if route has responseOutputSchema
61
- if (route.responseOutputSchema) {
62
- base.properties!.data = route.responseOutputSchema;
63
- }
60
+ // Add data field only if route has responseOutputSchema
61
+ if (route.responseOutputSchema) {
62
+ base.properties!.data = route.responseOutputSchema;
63
+ }
64
64
 
65
- // Add collect fields from current step
66
- if (currentStep?.collect) {
67
- if (agentSchema?.properties) {
68
- // Use agent schema definitions for collect fields
69
- for (const field of currentStep.collect) {
70
- const fieldSchema = agentSchema.properties[field as string];
71
- if (fieldSchema) {
72
- base.properties![field as string] = fieldSchema;
73
- }
74
- }
75
- } else {
76
- // No agent schema - generate dynamic schema from collect fields
77
- for (const field of currentStep.collect) {
78
- base.properties![field as string] = {
79
- type: "string",
80
- description: `Collected value for ${String(field)}`,
81
- };
65
+ // Add collect fields from current step
66
+ if (currentStep?.collect) {
67
+ if (agentSchema?.properties) {
68
+ // Use agent schema definitions for collect fields
69
+ for (const field of currentStep.collect) {
70
+ const fieldSchema = agentSchema.properties[field as string];
71
+ if (fieldSchema) {
72
+ base.properties![field as string] = fieldSchema;
82
73
  }
83
74
  }
75
+ } else {
76
+ // No agent schema - generate dynamic schema from collect fields
77
+ for (const field of currentStep.collect) {
78
+ base.properties![field as string] = {
79
+ type: "string",
80
+ description: `Collected value for ${String(field)}`,
81
+ };
82
+ }
84
83
  }
85
-
86
- return base;
87
84
  }
88
85
 
86
+ return base;
87
+ }
88
+
89
89
  async buildResponsePrompt(
90
90
  params: BuildResponsePromptParams<TContext, TData>
91
91
  ): Promise<string> {
@@ -155,11 +155,11 @@ export class ResponseEngine<TContext = unknown, TData = unknown> {
155
155
 
156
156
  await pc.addInteractionHistory(history);
157
157
  await pc.addLastMessage(lastMessage);
158
-
158
+
159
159
  // Add data collection instructions - include ALL route fields, not just current step
160
160
  // Collect all fields from route's required and optional fields
161
161
  const allRouteFields = new Set<string>();
162
-
162
+
163
163
  if (route.requiredFields) {
164
164
  route.requiredFields.forEach(field => allRouteFields.add(String(field)));
165
165
  }
@@ -173,7 +173,7 @@ export class ResponseEngine<TContext = unknown, TData = unknown> {
173
173
  if (allRouteFields.size > 0) {
174
174
  const stepCollectFields = new Set(currentStep?.collect?.map(f => String(f)) || []);
175
175
  const fieldDescriptions: string[] = [];
176
-
176
+
177
177
  for (const field of allRouteFields) {
178
178
  if (agentSchema?.properties) {
179
179
  const fieldSchema = agentSchema.properties[field];
@@ -181,22 +181,22 @@ export class ResponseEngine<TContext = unknown, TData = unknown> {
181
181
  const fieldName = field;
182
182
  const fieldDesc = fieldSchema.description || fieldName;
183
183
  const fieldType = Array.isArray(fieldSchema.type) ? fieldSchema.type[0] : fieldSchema.type;
184
-
184
+
185
185
  let fieldInfo = ` • ${fieldName} (${fieldType})`;
186
-
186
+
187
187
  // Add enum values if present
188
188
  if (fieldSchema.enum && Array.isArray(fieldSchema.enum)) {
189
189
  fieldInfo += ` [${fieldSchema.enum.join(' | ')}]`;
190
190
  }
191
-
191
+
192
192
  // Add description
193
193
  fieldInfo += `: ${fieldDesc}`;
194
-
194
+
195
195
  // Mark if this is the current step's focus
196
196
  if (stepCollectFields.has(field)) {
197
197
  fieldInfo += ` ← FOCUS FOR THIS STEP`;
198
198
  }
199
-
199
+
200
200
  fieldDescriptions.push(fieldInfo);
201
201
  }
202
202
  } else {
@@ -208,7 +208,7 @@ export class ResponseEngine<TContext = unknown, TData = unknown> {
208
208
  fieldDescriptions.push(fieldInfo);
209
209
  }
210
210
  }
211
-
211
+
212
212
  if (fieldDescriptions.length > 0) {
213
213
  const instruction = [
214
214
  `## Data Collection Rules`,
@@ -231,22 +231,22 @@ export class ResponseEngine<TContext = unknown, TData = unknown> {
231
231
  `- preferredDate: "next Tuesday"`,
232
232
  `- preferredTime: "2 PM"`,
233
233
  ].join('\n');
234
-
234
+
235
235
  await pc.addInstruction(instruction);
236
236
  }
237
237
  }
238
-
238
+
239
239
  // Add response format instructions with explicit JSON structure
240
240
  // Generate example JSON based on actual schema fields
241
241
  const exampleFields: string[] = [' "message": "your response to the user"'];
242
-
242
+
243
243
  for (const field of allRouteFields) {
244
244
  if (agentSchema?.properties) {
245
245
  const fieldSchema = agentSchema.properties[field];
246
246
  if (fieldSchema) {
247
247
  const fieldType = Array.isArray(fieldSchema.type) ? fieldSchema.type[0] : fieldSchema.type;
248
248
  let exampleValue = '"value if extracted"';
249
-
249
+
250
250
  // Generate type-appropriate example
251
251
  if (fieldSchema.enum && Array.isArray(fieldSchema.enum) && fieldSchema.enum.length > 0) {
252
252
  exampleValue = `"${fieldSchema.enum[0]}"`;
@@ -257,7 +257,7 @@ export class ResponseEngine<TContext = unknown, TData = unknown> {
257
257
  } else if (fieldType === 'boolean') {
258
258
  exampleValue = 'true';
259
259
  }
260
-
260
+
261
261
  exampleFields.push(` "${field}": ${exampleValue}`);
262
262
  }
263
263
  } else {
@@ -265,7 +265,7 @@ export class ResponseEngine<TContext = unknown, TData = unknown> {
265
265
  exampleFields.push(` "${field}": "extracted value"`);
266
266
  }
267
267
  }
268
-
268
+
269
269
  await pc.addInstruction(
270
270
  [
271
271
  `## Response Format`,
@@ -277,14 +277,17 @@ export class ResponseEngine<TContext = unknown, TData = unknown> {
277
277
  ``,
278
278
  `CRITICAL RULES:`,
279
279
  `- Return ONLY the JSON object, no other text`,
280
- `- The "message" field is REQUIRED and must contain your response to the user`,
281
- `- Include ALL extracted data fields as top-level properties`,
280
+ `- The "message" field is REQUIRED and must contain a natural, conversational response directed at the user`,
281
+ `- The "message" MUST read like a human conversation - warm, natural, and contextual`,
282
+ `- NEVER include field names, JSON keys, schema properties, raw data values, or technical/internal information in the "message"`,
283
+ `- NEVER echo back data in the format "fieldName: value" in the "message" - data goes in the JSON fields, not the message`,
284
+ `- Include ALL extracted data fields as separate top-level JSON properties (NOT inside the message text)`,
282
285
  `- Only include data fields that were actually mentioned by the user`,
283
286
  `- Do not wrap the JSON in markdown code blocks`,
284
287
  `- Do not add any explanatory text before or after the JSON`,
285
288
  ].join('\n')
286
289
  );
287
-
290
+
288
291
  return pc.build();
289
292
  }
290
293