@falai/agent 4.0.0-alpha.2 → 4.0.0-alpha.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.
- package/dist/cjs/core/FlowSpec.d.ts.map +1 -1
- package/dist/cjs/core/FlowSpec.js +12 -0
- package/dist/cjs/core/FlowSpec.js.map +1 -1
- package/dist/cjs/core/Prompt.d.ts.map +1 -1
- package/dist/cjs/core/Prompt.js +7 -1
- package/dist/cjs/core/Prompt.js.map +1 -1
- package/dist/cjs/core/Speak.d.ts.map +1 -1
- package/dist/cjs/core/Speak.js +11 -2
- package/dist/cjs/core/Speak.js.map +1 -1
- package/dist/cjs/core/Understand.d.ts.map +1 -1
- package/dist/cjs/core/Understand.js +13 -7
- package/dist/cjs/core/Understand.js.map +1 -1
- package/dist/cjs/utils/phrases.d.ts +25 -0
- package/dist/cjs/utils/phrases.d.ts.map +1 -0
- package/dist/cjs/utils/phrases.js +38 -0
- package/dist/cjs/utils/phrases.js.map +1 -0
- package/dist/core/FlowSpec.d.ts.map +1 -1
- package/dist/core/FlowSpec.js +12 -0
- package/dist/core/FlowSpec.js.map +1 -1
- package/dist/core/Prompt.d.ts.map +1 -1
- package/dist/core/Prompt.js +7 -1
- package/dist/core/Prompt.js.map +1 -1
- package/dist/core/Speak.d.ts.map +1 -1
- package/dist/core/Speak.js +11 -2
- package/dist/core/Speak.js.map +1 -1
- package/dist/core/Understand.d.ts.map +1 -1
- package/dist/core/Understand.js +13 -7
- package/dist/core/Understand.js.map +1 -1
- package/dist/utils/phrases.d.ts +25 -0
- package/dist/utils/phrases.d.ts.map +1 -0
- package/dist/utils/phrases.js +35 -0
- package/dist/utils/phrases.js.map +1 -0
- package/docs/guides/error-handling.md +2 -0
- package/docs/migration/v3-to-v4.md +5 -2
- package/docs/reference/trigger.md +20 -0
- package/package.json +1 -1
- package/src/core/FlowSpec.ts +18 -0
- package/src/core/Prompt.ts +7 -1
- package/src/core/Speak.ts +11 -2
- package/src/core/Understand.ts +9 -5
- package/src/utils/phrases.ts +40 -0
package/src/core/Prompt.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import type { AgentOptions } from "../types/agent.js";
|
|
11
11
|
import type { FieldDef, Instruction } from "../types/flow.js";
|
|
12
|
+
import { splitPhrases } from "../utils/phrases.js";
|
|
12
13
|
import { isKnown } from "../utils/schema.js";
|
|
13
14
|
import { render, type TemplateScope } from "../utils/template.js";
|
|
14
15
|
|
|
@@ -106,7 +107,12 @@ export function instructionsSection<C, D>(groups: InstructionGroup<C, D>[], scop
|
|
|
106
107
|
const text = render(item.prompt, scope).trim();
|
|
107
108
|
if (!text) continue;
|
|
108
109
|
const when = item.when === undefined ? [] : Array.isArray(item.when) ? item.when : [item.when];
|
|
109
|
-
const
|
|
110
|
+
const { counts, excludes } = splitPhrases(when);
|
|
111
|
+
const clauses = [
|
|
112
|
+
...(counts.length ? [`apply only when: ${counts.join(" OR ")}`] : []),
|
|
113
|
+
...(excludes.length ? [`never when: ${excludes.join(" OR ")}`] : []),
|
|
114
|
+
];
|
|
115
|
+
const condition = clauses.length ? ` (${clauses.join("; ")})` : "";
|
|
110
116
|
lines.push(`- [${item.kind ?? "should"}] ${group.caption} ${text}${condition}`);
|
|
111
117
|
}
|
|
112
118
|
}
|
package/src/core/Speak.ts
CHANGED
|
@@ -78,8 +78,17 @@ function deferralOf(error: unknown): Deferral {
|
|
|
78
78
|
// A usage window that says when it reopens is worth exactly one wake, then.
|
|
79
79
|
// Without that number, waiting is guessing, and the ladder guesses in minutes
|
|
80
80
|
// at a limit measured in hours.
|
|
81
|
-
const
|
|
82
|
-
|
|
81
|
+
const inferred = RETRY_KINDS.has(kind) || (kind === "quota" && reset !== undefined);
|
|
82
|
+
// Unless the provider said so outright. `x-should-retry` is the one answer
|
|
83
|
+
// nobody has to infer, and core already puts it above its own transience
|
|
84
|
+
// test — a 503 that says don't costs five wakes and ten model calls here if
|
|
85
|
+
// this reads the kind instead.
|
|
86
|
+
const stated = error instanceof ProviderError ? error.shouldRetry : undefined;
|
|
87
|
+
return {
|
|
88
|
+
code: DEFER_CODE[kind],
|
|
89
|
+
retryable: stated ?? inferred,
|
|
90
|
+
...(reset !== undefined ? { resetAtMs: reset } : {}),
|
|
91
|
+
};
|
|
83
92
|
}
|
|
84
93
|
/** Gemini rejects any other envelope property name. */
|
|
85
94
|
const WIRE_NAME = /^[a-zA-Z0-9_-]+$/;
|
package/src/core/Understand.ts
CHANGED
|
@@ -23,6 +23,7 @@ import type { FieldDef, FieldDefs, Flow, ParamDef, ParamDefs } from "../types/fl
|
|
|
23
23
|
import type { StructuredSchema } from "../types/schema.js";
|
|
24
24
|
import { extractEmbeddedJSONObject, isRecord } from "../utils/json.js";
|
|
25
25
|
import { logger } from "../utils/logger.js";
|
|
26
|
+
import { splitPhrases } from "../utils/phrases.js";
|
|
26
27
|
import { coerceField, isKnown, pendingFields, toWireSchema } from "../utils/schema.js";
|
|
27
28
|
import { render, type TemplateScope } from "../utils/template.js";
|
|
28
29
|
import { readUsage } from "../utils/usage.js";
|
|
@@ -245,8 +246,9 @@ function flowsSection<C, D>(candidates: Flow<C, D>[], aliases: Aliases, t: (text
|
|
|
245
246
|
];
|
|
246
247
|
candidates.forEach((flow, i) => {
|
|
247
248
|
lines.push(`${i + 1}. ${aliases.of(flow.id, "f")} — ${flow.name}${flow.description ? `: ${t(flow.description)}` : ""}`);
|
|
248
|
-
const
|
|
249
|
-
if (
|
|
249
|
+
const { counts, excludes } = splitPhrases(triggerPhrases(flow, "message"));
|
|
250
|
+
if (counts.length) lines.push(` The customer: ${counts.map(t).join("; ")}`);
|
|
251
|
+
if (excludes.length) lines.push(` Score 0 when: ${excludes.map(t).join("; ")}`);
|
|
250
252
|
});
|
|
251
253
|
lines.push(
|
|
252
254
|
"",
|
|
@@ -265,12 +267,14 @@ function mentionsSection<C, D>(flows: Flow<C, D>[], aliases: Aliases, t: (text:
|
|
|
265
267
|
const lines = [
|
|
266
268
|
"## Things the customer may mention",
|
|
267
269
|
"For each item, answer true when the customer's message clearly brings it up, false otherwise. " +
|
|
268
|
-
"Be conservative: true needs clear, explicit evidence in the message. The phrases under an item are alternatives; one match is enough."
|
|
270
|
+
"Be conservative: true needs clear, explicit evidence in the message. The phrases under an item are alternatives; one match is enough. " +
|
|
271
|
+
"A 'Does not count when' line overrides a match: if one of those fits, answer false.",
|
|
269
272
|
];
|
|
270
273
|
for (const flow of flows) {
|
|
271
274
|
lines.push(`- ${aliases.of(flow.id, "f")} — ${flow.name}${flow.description ? `: ${t(flow.description)}` : ""}`);
|
|
272
|
-
const
|
|
273
|
-
if (
|
|
275
|
+
const { counts, excludes } = splitPhrases(triggerPhrases(flow, "mention"));
|
|
276
|
+
if (counts.length) lines.push(` Counts when: ${counts.map(t).join("; ")}`);
|
|
277
|
+
if (excludes.length) lines.push(` Does not count when: ${excludes.map(t).join("; ")}`);
|
|
274
278
|
const defs = extractDefs(flow);
|
|
275
279
|
if (defs) {
|
|
276
280
|
lines.push(" When true, also extract:");
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Splitting an AI-judged phrase list into what counts and what rules it out.
|
|
3
|
+
*
|
|
4
|
+
* A trigger's phrases are alternatives: one match is enough. That makes the
|
|
5
|
+
* list useless for saying "but not this" — and "but not this" is what keeps a
|
|
6
|
+
* classifier honest. A customer answering "pode sim" to an offer of a meeting
|
|
7
|
+
* is agreeing to the meeting, not asking for a human, and without a way to say
|
|
8
|
+
* so every cheerful yes reads as a handoff request.
|
|
9
|
+
*
|
|
10
|
+
* So a phrase that opens with `!` is an exclusion: it does not make the trigger
|
|
11
|
+
* fire, it stops it. Exclusions win over matches, and the model is told both
|
|
12
|
+
* lists separately.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface Phrases {
|
|
16
|
+
/** Any one of these makes it true. */
|
|
17
|
+
counts: string[];
|
|
18
|
+
/** Any one of these makes it false, whatever matched. */
|
|
19
|
+
excludes: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Split a phrase list on the leading `!`. Blank entries and a bare `"!"` are
|
|
24
|
+
* dropped: they would render as an empty bullet and mean nothing to the model.
|
|
25
|
+
*/
|
|
26
|
+
export function splitPhrases(phrases: readonly string[]): Phrases {
|
|
27
|
+
const counts: string[] = [];
|
|
28
|
+
const excludes: string[] = [];
|
|
29
|
+
for (const phrase of phrases) {
|
|
30
|
+
const trimmed = phrase.trim();
|
|
31
|
+
if (!trimmed) continue;
|
|
32
|
+
if (trimmed.startsWith("!")) {
|
|
33
|
+
const body = trimmed.slice(1).trim();
|
|
34
|
+
if (body) excludes.push(body);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
counts.push(trimmed);
|
|
38
|
+
}
|
|
39
|
+
return { counts, excludes };
|
|
40
|
+
}
|