@guava-ai/guava-sdk 0.24.0 → 0.26.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/README.md +13 -3
- package/bin/example-runner.ts +138 -11
- package/dist/bin/example-runner.d.ts +23 -6
- package/dist/bin/example-runner.js +112 -9
- package/dist/bin/example-runner.js.map +1 -1
- package/dist/examples/{example.test.js → agent-testing.test.js} +2 -2
- package/dist/examples/agent-testing.test.js.map +1 -0
- package/dist/examples/multiple-agents.d.ts +1 -0
- package/dist/examples/multiple-agents.js +52 -0
- package/dist/examples/multiple-agents.js.map +1 -0
- package/dist/examples/scheduling-outbound.js +3 -2
- package/dist/examples/scheduling-outbound.js.map +1 -1
- package/dist/src/agent.d.ts +17 -6
- package/dist/src/agent.js +106 -41
- package/dist/src/agent.js.map +1 -1
- package/dist/src/call.d.ts +3 -0
- package/dist/src/call.js +63 -23
- package/dist/src/call.js.map +1 -1
- package/dist/src/client.d.ts +3 -0
- package/dist/src/client.js +6 -0
- package/dist/src/client.js.map +1 -1
- package/dist/src/commands.d.ts +11 -0
- package/dist/src/commands.js +6 -1
- package/dist/src/commands.js.map +1 -1
- package/dist/src/example-utils.d.ts +2 -0
- package/dist/src/example-utils.js +115 -0
- package/dist/src/example-utils.js.map +1 -0
- package/dist/src/helpers/llm.d.ts +10 -0
- package/dist/src/helpers/llm.js +54 -27
- package/dist/src/helpers/llm.js.map +1 -1
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +4 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/runner.d.ts +10 -0
- package/dist/src/runner.js +101 -0
- package/dist/src/runner.js.map +1 -0
- package/dist/src/testing/session.d.ts +7 -0
- package/dist/src/testing/session.js +3 -22
- package/dist/src/testing/session.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/examples/README.md +7 -6
- package/examples/{example.test.ts → agent-testing.test.ts} +1 -1
- package/examples/multiple-agents.ts +18 -0
- package/examples/scheduling-outbound.ts +3 -2
- package/package.json +5 -1
- package/src/agent.ts +130 -47
- package/src/call.ts +78 -23
- package/src/client.ts +7 -0
- package/src/commands.ts +7 -0
- package/src/example-utils.ts +87 -0
- package/src/helpers/llm.ts +23 -31
- package/src/index.ts +3 -1
- package/src/runner.ts +39 -0
- package/src/testing/session.ts +2 -22
- package/src/version.ts +1 -1
- package/dist/examples/example.test.js.map +0 -1
- /package/dist/examples/{example.test.d.ts → agent-testing.test.d.ts} +0 -0
|
@@ -33,12 +33,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.TestSession = void 0;
|
|
36
|
+
exports.TestSession = exports._evalResponseSchema = void 0;
|
|
37
37
|
const node_crypto_1 = require("node:crypto");
|
|
38
38
|
const z = __importStar(require("zod"));
|
|
39
39
|
const protocol_ts_1 = require("./protocol.js");
|
|
40
40
|
const llm_ts_1 = require("../helpers/llm.js");
|
|
41
|
-
|
|
41
|
+
exports._evalResponseSchema = z.object({
|
|
42
42
|
results: z.array(z.object({
|
|
43
43
|
passed: z.boolean(),
|
|
44
44
|
reasoning: z.string().optional(),
|
|
@@ -170,26 +170,7 @@ ${transcript || "(empty — no conversation occurred)"}
|
|
|
170
170
|
|
|
171
171
|
Criteria:
|
|
172
172
|
${criteriaList}`;
|
|
173
|
-
const
|
|
174
|
-
type: "object",
|
|
175
|
-
properties: {
|
|
176
|
-
results: {
|
|
177
|
-
type: "array",
|
|
178
|
-
items: {
|
|
179
|
-
type: "object",
|
|
180
|
-
properties: {
|
|
181
|
-
passed: { type: "boolean" },
|
|
182
|
-
reasoning: { type: "string" },
|
|
183
|
-
},
|
|
184
|
-
required: ["passed"],
|
|
185
|
-
additionalProperties: false,
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
required: ["results"],
|
|
190
|
-
additionalProperties: false,
|
|
191
|
-
};
|
|
192
|
-
const evalResponse = _evalResponseSchema.parse(JSON.parse(await (0, llm_ts_1._generate)(this._client, prompt, evalSchema)));
|
|
173
|
+
const evalResponse = exports._evalResponseSchema.parse(JSON.parse(await (0, llm_ts_1._generate)(this._client, prompt, z.toJSONSchema(exports._evalResponseSchema))));
|
|
193
174
|
if (evalResponse.results.length !== allCriteria.length) {
|
|
194
175
|
throw new Error(`Evaluation returned ${evalResponse.results.length} results for ${allCriteria.length} criteria.`);
|
|
195
176
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/testing/session.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,6CAA0C;AAC1C,uCAAyB;AACzB,+CAA0E;AAE1E,8CAA8C;
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/testing/session.ts"],"names":[],"mappings":"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"}
|
package/dist/src/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "0.
|
|
1
|
+
export declare const SDK_VERSION = "0.26.0";
|
package/dist/src/version.js
CHANGED
package/examples/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Examples
|
|
2
2
|
|
|
3
|
-
- [
|
|
4
|
-
- [help-desk.ts](./help-desk.ts) -
|
|
5
|
-
- [
|
|
6
|
-
- [
|
|
7
|
-
- [
|
|
8
|
-
- [
|
|
3
|
+
- [agent-testing.test.ts](./agent-testing.test.ts) - Test suite for the help-desk agent demonstrating `agent.test()`, `agent.testRoleplay()`, `agent.patch()`, handler unit tests with `MockCall`, and LLM-evaluated assertions with `session.evaluate()`.
|
|
4
|
+
- [help-desk.ts](./help-desk.ts) - Inbound call agent for a furniture retailer that uses `DocumentQA` for RAG-backed question answering and `IntentRecognizer` to route callers to the correct department (sales, delivery, account management).
|
|
5
|
+
- [multiple-agents.ts](./multiple-agents.ts) - Demonstrates running two independent agents simultaneously on different connection types (phone and WebRTC) using a shared `Runner`.
|
|
6
|
+
- [polling-campaign.ts](./polling-campaign.ts) - Outbound campaign agent that conducts a non-partisan political opinion poll, uses `reachPerson` to confirm contact availability, and collects structured responses via a task checklist.
|
|
7
|
+
- [property-insurance.ts](./property-insurance.ts) - Inbound call agent for a property insurance company that uses `DocumentQA` to answer policy questions from a knowledge base.
|
|
8
|
+
- [restaurant-waitlist.ts](./restaurant-waitlist.ts) - Inbound call agent for a restaurant that collects caller name, party size, and phone number to add callers to a waitlist using a structured task.
|
|
9
|
+
- [scheduling-outbound.ts](./scheduling-outbound.ts) - Outbound call agent for a dental office that uses `reachPerson` to confirm contact availability, then schedules appointments using a searchable calendar-slot field backed by `DatetimeFilter`.
|
|
@@ -10,7 +10,7 @@ import { agent } from "@guava-ai/guava-sdk/examples/help-desk";
|
|
|
10
10
|
describe("HelpDeskAgent", () => {
|
|
11
11
|
test("handler unit test - new purchase routes to sales", async () => {
|
|
12
12
|
const suggestion = await agent.handlers.onActionRequest(new MockCall(), "make a new purchase");
|
|
13
|
-
expect(suggestion).
|
|
13
|
+
expect(suggestion).toContainEqual({ key: "sales" });
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
test("new purchase routes to sales", async () => {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as guava from "@guava-ai/guava-sdk";
|
|
2
|
+
|
|
3
|
+
const agentA = new guava.Agent({
|
|
4
|
+
name: "Grace",
|
|
5
|
+
purpose: "You are a helpful voice agent.",
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const agentB = new guava.Agent({
|
|
9
|
+
name: "Jordan",
|
|
10
|
+
purpose: "You are a helpful voice agent.",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export async function run(_args: string[]) {
|
|
14
|
+
const runner = new guava.Runner();
|
|
15
|
+
runner.listenPhone(agentA, process.env.GUAVA_AGENT_NUMBER!);
|
|
16
|
+
runner.listenWebrtc(agentB);
|
|
17
|
+
await runner.run();
|
|
18
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as guava from "@guava-ai/guava-sdk";
|
|
2
2
|
import { DatetimeFilter } from "@guava-ai/guava-sdk/helpers";
|
|
3
3
|
import { mockAppointmentsForFuture } from "@guava-ai/guava-sdk/example-data";
|
|
4
|
+
import { getAgentNumber } from "@guava-ai/guava-sdk/example-utils";
|
|
4
5
|
|
|
5
6
|
const agent = new guava.Agent({
|
|
6
7
|
organization: "Bright Smile Dental",
|
|
@@ -33,7 +34,7 @@ agent.onReachPerson(async (call: guava.Call, outcome: string) => {
|
|
|
33
34
|
],
|
|
34
35
|
});
|
|
35
36
|
} else {
|
|
36
|
-
await call.hangup("
|
|
37
|
+
await call.hangup("Appropriately end the call.");
|
|
37
38
|
}
|
|
38
39
|
});
|
|
39
40
|
|
|
@@ -56,7 +57,7 @@ export async function run(args: string[]) {
|
|
|
56
57
|
process.exit(1);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
agent.callPhone(
|
|
60
|
+
agent.callPhone(await getAgentNumber(), toNumber, {
|
|
60
61
|
patientName: patientName,
|
|
61
62
|
});
|
|
62
63
|
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@guava-ai/guava-sdk",
|
|
3
3
|
"description": "The official TypeScript SDK for building Guava voice agents.",
|
|
4
4
|
"homepage": "https://docs.goguava.ai/typescript-sdk/",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.26.0",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"main": "dist/src/index.js",
|
|
8
8
|
"types": "dist/src/index.d.ts",
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
"types": "./dist/src/example-data.d.ts",
|
|
24
24
|
"default": "./dist/src/example-data.js"
|
|
25
25
|
},
|
|
26
|
+
"./example-utils": {
|
|
27
|
+
"types": "./dist/src/example-utils.d.ts",
|
|
28
|
+
"default": "./dist/src/example-utils.js"
|
|
29
|
+
},
|
|
26
30
|
"./examples/help-desk": {
|
|
27
31
|
"types": "./dist/examples/help-desk.d.ts",
|
|
28
32
|
"default": "./dist/examples/help-desk.js"
|
package/src/agent.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
AnswerQuestionCommand,
|
|
5
5
|
ChoiceResultCommand,
|
|
6
6
|
type Command,
|
|
7
|
+
ExpertErrorCommand,
|
|
7
8
|
RegisteredHooksCommand,
|
|
8
9
|
} from "./commands.ts";
|
|
9
10
|
import {
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
type DTMFPressedEvent,
|
|
14
15
|
type EscalateEvent,
|
|
15
16
|
type GuavaEvent,
|
|
17
|
+
type OutboundCallFailed,
|
|
16
18
|
decodeEventDict,
|
|
17
19
|
} from "./events.ts";
|
|
18
20
|
import {
|
|
@@ -31,9 +33,15 @@ import { telemetryClient } from "./telemetry.ts";
|
|
|
31
33
|
import { runChat } from "./testing/chat.ts";
|
|
32
34
|
import { SessionStarted } from "./testing/protocol.ts";
|
|
33
35
|
import { TestSession } from "./testing/session.ts";
|
|
36
|
+
import * as z from "zod";
|
|
34
37
|
import { fetchOrThrow, getBaseUrl } from "./utils.ts";
|
|
35
38
|
import { runWebrtcHelper } from "./webrtc-helper.ts";
|
|
36
39
|
|
|
40
|
+
export const _roleplayActionSchema = z.object({
|
|
41
|
+
action: z.enum(["speak", "hangup"]),
|
|
42
|
+
utterance: z.string().optional(),
|
|
43
|
+
});
|
|
44
|
+
|
|
37
45
|
export type { CallInfo } from "./socket/call-info.ts";
|
|
38
46
|
|
|
39
47
|
export type IncomingCallAction = { action: "accept" } | { action: "decline" };
|
|
@@ -57,7 +65,7 @@ export class Agent {
|
|
|
57
65
|
private _acceptDtmfForNumbers: boolean;
|
|
58
66
|
private _logger: Logger;
|
|
59
67
|
|
|
60
|
-
|
|
68
|
+
_client: Client = new Client();
|
|
61
69
|
|
|
62
70
|
private _onCallReceived: (callInfo: CallInfo) => Promise<IncomingCallAction> = async () => ({
|
|
63
71
|
action: "accept",
|
|
@@ -85,6 +93,7 @@ export class Agent {
|
|
|
85
93
|
private _onSessionEnd?: (call: Call, event: BotSessionEnded) => Promise<void>;
|
|
86
94
|
private _onEscalate?: (call: Call, event: EscalateEvent) => Promise<void>;
|
|
87
95
|
private _onDtmf?: (call: Call, event: DTMFPressedEvent) => Promise<void>;
|
|
96
|
+
private _onOutboundFailed?: (event: OutboundCallFailed) => Promise<void>;
|
|
88
97
|
|
|
89
98
|
constructor(args?: {
|
|
90
99
|
name?: string;
|
|
@@ -188,6 +197,10 @@ export class Agent {
|
|
|
188
197
|
this._onDtmf = callback;
|
|
189
198
|
}
|
|
190
199
|
|
|
200
|
+
onOutboundFailed(callback: (event: OutboundCallFailed) => Promise<void>): void {
|
|
201
|
+
this._onOutboundFailed = callback;
|
|
202
|
+
}
|
|
203
|
+
|
|
191
204
|
get handlers() {
|
|
192
205
|
return {
|
|
193
206
|
onCallReceived: (callInfo: CallInfo) => this._onCallReceived(callInfo),
|
|
@@ -272,14 +285,37 @@ export class Agent {
|
|
|
272
285
|
await runWebrtcHelper(webrtcCode, getBaseUrl());
|
|
273
286
|
}
|
|
274
287
|
|
|
288
|
+
private async _invokeHandler(
|
|
289
|
+
call: Call,
|
|
290
|
+
name: string,
|
|
291
|
+
informAgent: boolean,
|
|
292
|
+
handler: () => Promise<void>,
|
|
293
|
+
): Promise<void> {
|
|
294
|
+
try {
|
|
295
|
+
await handler();
|
|
296
|
+
} catch (err) {
|
|
297
|
+
this._logger.error(`An error occurred in the ${name} handler.`, err);
|
|
298
|
+
if (informAgent) {
|
|
299
|
+
await call.sendCommand(ExpertErrorCommand, {
|
|
300
|
+
command_type: "expert-error",
|
|
301
|
+
message: `The expert encountered an error while processing ${name}`,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
275
307
|
private async _dispatchEvent(call: Call, event: GuavaEvent, testSession?: TestSession) {
|
|
276
308
|
if (event.event_type === "caller-speech") {
|
|
277
309
|
if (this._onCallerSpeech !== undefined) {
|
|
278
|
-
await this.
|
|
310
|
+
await this._invokeHandler(call, "onCallerSpeech", false, () =>
|
|
311
|
+
this._onCallerSpeech!(call, event),
|
|
312
|
+
);
|
|
279
313
|
}
|
|
280
314
|
} else if (event.event_type === "agent-speech") {
|
|
281
315
|
if (this._onAgentSpeech !== undefined) {
|
|
282
|
-
await this.
|
|
316
|
+
await this._invokeHandler(call, "onAgentSpeech", false, () =>
|
|
317
|
+
this._onAgentSpeech!(call, event),
|
|
318
|
+
);
|
|
283
319
|
}
|
|
284
320
|
} else if (event.event_type === "task-done") {
|
|
285
321
|
const errors: string[] = [];
|
|
@@ -298,17 +334,28 @@ export class Agent {
|
|
|
298
334
|
await call.retryTask(errors.join(" "));
|
|
299
335
|
} else {
|
|
300
336
|
this._logger.info(`Task ${event.task_id} completed.`);
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
337
|
+
try {
|
|
338
|
+
if (this._onTaskCompleteGeneric !== undefined) {
|
|
339
|
+
await this._onTaskCompleteGeneric(call, event.task_id);
|
|
340
|
+
} else if (event.task_id in this._onTaskCompleteHandlers) {
|
|
341
|
+
await this._onTaskCompleteHandlers[event.task_id](call);
|
|
342
|
+
} else {
|
|
343
|
+
this._logger.warn(`No handler registered for completion of task '${event.task_id}'`);
|
|
344
|
+
}
|
|
345
|
+
} catch (err) {
|
|
346
|
+
this._logger.error(
|
|
347
|
+
`An error occurred in the on_task_complete('${event.task_id}') handler.`,
|
|
348
|
+
err,
|
|
349
|
+
);
|
|
350
|
+
await call.sendCommand(ExpertErrorCommand, {
|
|
351
|
+
command_type: "expert-error",
|
|
352
|
+
message: `The expert encountered an error while processing on_task_complete('${event.task_id}') - the task has failed.`,
|
|
353
|
+
});
|
|
307
354
|
}
|
|
308
355
|
}
|
|
309
356
|
} else if (event.event_type === "agent-question") {
|
|
357
|
+
this._logger.info(`Received question from bot: ${event.question}`);
|
|
310
358
|
if (this._onQuestion !== undefined) {
|
|
311
|
-
this._logger.info(`Received question from bot: ${event.question}`);
|
|
312
359
|
let answer: string;
|
|
313
360
|
try {
|
|
314
361
|
answer = await this._onQuestion(call, event.question);
|
|
@@ -333,6 +380,9 @@ export class Agent {
|
|
|
333
380
|
}
|
|
334
381
|
} else if (event.event_type === "action-item-done") {
|
|
335
382
|
call._fieldValues[event.key] = event.payload;
|
|
383
|
+
if (event.key && event.payload) {
|
|
384
|
+
this._logger.info(`Field ${event.key} updated.`);
|
|
385
|
+
}
|
|
336
386
|
} else if (event.event_type === "choice-query") {
|
|
337
387
|
this._logger.info(`Received search query for field '${event.field_key}': ${event.query}`);
|
|
338
388
|
const handler = this._searchQueryHandlers[event.field_key];
|
|
@@ -340,25 +390,49 @@ export class Agent {
|
|
|
340
390
|
this._logger.warn(
|
|
341
391
|
`Search query arrived for field '${event.field_key}' with no handler attached.`,
|
|
342
392
|
);
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
command_type: "choice-query-result",
|
|
347
|
-
field_key: event.field_key,
|
|
348
|
-
query_id: event.query_id,
|
|
349
|
-
matched_choices: matchedChoices,
|
|
350
|
-
other_choices: otherChoices,
|
|
393
|
+
await call.sendCommand(ExpertErrorCommand, {
|
|
394
|
+
command_type: "expert-error",
|
|
395
|
+
message: `The expert failed to handle on_search_query('${event.field_key}'). No results will be forthcoming.`,
|
|
351
396
|
});
|
|
397
|
+
} else {
|
|
398
|
+
try {
|
|
399
|
+
const [matchedChoices, otherChoices] = await handler(call, event.query);
|
|
400
|
+
await call.sendCommand(ChoiceResultCommand, {
|
|
401
|
+
command_type: "choice-query-result",
|
|
402
|
+
field_key: event.field_key,
|
|
403
|
+
query_id: event.query_id,
|
|
404
|
+
matched_choices: matchedChoices,
|
|
405
|
+
other_choices: otherChoices,
|
|
406
|
+
});
|
|
407
|
+
} catch (err) {
|
|
408
|
+
this._logger.error(
|
|
409
|
+
`An error occurred in the on_search_query('${event.field_key}') handler.`,
|
|
410
|
+
err,
|
|
411
|
+
);
|
|
412
|
+
await call.sendCommand(ExpertErrorCommand, {
|
|
413
|
+
command_type: "expert-error",
|
|
414
|
+
message: `The expert encountered an error while processing the on_search_query('${event.field_key}') handler. No results will be forthcoming.`,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
352
417
|
}
|
|
353
418
|
} else if (event.event_type === "action-request") {
|
|
354
419
|
this._logger.info(`Received action request ${event.intent_id}: ${event.intent_summary}`);
|
|
355
420
|
let suggestions: SuggestedAction[] = [];
|
|
356
421
|
if (this._onActionRequested !== undefined) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
422
|
+
try {
|
|
423
|
+
const result = await this._onActionRequested(call, event.intent_summary);
|
|
424
|
+
if (Array.isArray(result)) {
|
|
425
|
+
suggestions = result;
|
|
426
|
+
} else if (result !== null && result !== undefined) {
|
|
427
|
+
suggestions = [result];
|
|
428
|
+
}
|
|
429
|
+
} catch (err) {
|
|
430
|
+
this._logger.error("An error occurred in the onActionRequest handler.", err);
|
|
431
|
+
await call.sendCommand(ExpertErrorCommand, {
|
|
432
|
+
command_type: "expert-error",
|
|
433
|
+
message:
|
|
434
|
+
"The expert encountered an error while processing the on_action_request handler.",
|
|
435
|
+
});
|
|
362
436
|
}
|
|
363
437
|
}
|
|
364
438
|
await call.sendCommand(ActionSuggestionCommand, {
|
|
@@ -378,7 +452,18 @@ export class Agent {
|
|
|
378
452
|
onActionFunc = () => this._onActionHandlers[event.action_key](call);
|
|
379
453
|
}
|
|
380
454
|
if (onActionFunc !== undefined) {
|
|
381
|
-
|
|
455
|
+
try {
|
|
456
|
+
await onActionFunc();
|
|
457
|
+
} catch (err) {
|
|
458
|
+
this._logger.error(
|
|
459
|
+
`An error occurred in the on_action('${event.action_key}') handler.`,
|
|
460
|
+
err,
|
|
461
|
+
);
|
|
462
|
+
await call.sendCommand(ExpertErrorCommand, {
|
|
463
|
+
command_type: "expert-error",
|
|
464
|
+
message: `The expert encountered an error while processing the on_action('${event.action_key}') handler.`,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
382
467
|
} else {
|
|
383
468
|
this._logger.warn(`No handler registered for action '${event.action_key}'`);
|
|
384
469
|
}
|
|
@@ -387,14 +472,18 @@ export class Agent {
|
|
|
387
472
|
if (testSession) {
|
|
388
473
|
testSession.terminationReason = event.termination_reason;
|
|
389
474
|
}
|
|
390
|
-
|
|
475
|
+
if (this._onSessionEnd !== undefined) {
|
|
476
|
+
await this._invokeHandler(call, "onSessionEnd", false, () =>
|
|
477
|
+
this._onSessionEnd!(call, event),
|
|
478
|
+
);
|
|
479
|
+
}
|
|
391
480
|
} else if (event.event_type === "dtmf") {
|
|
392
481
|
if (this._onDtmf !== undefined) {
|
|
393
|
-
await this._onDtmf(call, event);
|
|
482
|
+
await this._invokeHandler(call, "onDtmf", false, () => this._onDtmf!(call, event));
|
|
394
483
|
}
|
|
395
484
|
} else if (event.event_type === "escalate") {
|
|
396
485
|
if (this._onEscalate !== undefined) {
|
|
397
|
-
await this._onEscalate(call, event);
|
|
486
|
+
await this._invokeHandler(call, "onEscalate", true, () => this._onEscalate!(call, event));
|
|
398
487
|
} else if (event.requested_by === "agent") {
|
|
399
488
|
await call.sendInstruction(
|
|
400
489
|
"No escalation target set. Apologize for not being able to help, ask them to try calling another time, and hang up the call immediately.",
|
|
@@ -404,6 +493,13 @@ export class Agent {
|
|
|
404
493
|
"Let them know there are no representatives available to take their call. Ask them if they would prefer to continue or to call another time.",
|
|
405
494
|
);
|
|
406
495
|
}
|
|
496
|
+
} else if (event.event_type === "outbound-call-failed") {
|
|
497
|
+
this._logger.error(`Outbound call failed: ${event.error_reason}`);
|
|
498
|
+
if (this._onOutboundFailed !== undefined) {
|
|
499
|
+
await this._invokeHandler(call, "onOutboundFailed", false, () =>
|
|
500
|
+
this._onOutboundFailed!(event),
|
|
501
|
+
);
|
|
502
|
+
}
|
|
407
503
|
} else if (event.event_type === "error") {
|
|
408
504
|
this._logger.error(`The Guava agent reported an error: ${event.content}`);
|
|
409
505
|
} else if (event.event_type === "warning") {
|
|
@@ -572,12 +668,12 @@ export class Agent {
|
|
|
572
668
|
* @description use the Guava API to call out to a number
|
|
573
669
|
*/
|
|
574
670
|
async callPhone(
|
|
575
|
-
fromNumber: string
|
|
671
|
+
fromNumber: string,
|
|
576
672
|
toNumber: string,
|
|
577
673
|
variables: Record<string, any> = {},
|
|
578
674
|
): Promise<void> {
|
|
579
675
|
const url = new URL("v2/create-outbound", this._client.getHttpBase());
|
|
580
|
-
|
|
676
|
+
url.searchParams.set("from_number", fromNumber);
|
|
581
677
|
url.searchParams.set("to_number", toNumber);
|
|
582
678
|
|
|
583
679
|
const response = await fetchOrThrow(url, {
|
|
@@ -681,16 +777,6 @@ export class Agent {
|
|
|
681
777
|
roleplayPrompt: string,
|
|
682
778
|
variables: Record<string, any> = {},
|
|
683
779
|
): Promise<TestSession> {
|
|
684
|
-
const roleplaySchema = {
|
|
685
|
-
type: "object",
|
|
686
|
-
properties: {
|
|
687
|
-
action: { type: "string", enum: ["speak", "hangup"] },
|
|
688
|
-
utterance: { type: "string" },
|
|
689
|
-
},
|
|
690
|
-
required: ["action"],
|
|
691
|
-
additionalProperties: false,
|
|
692
|
-
};
|
|
693
|
-
|
|
694
780
|
return this.test(async (session) => {
|
|
695
781
|
let snapshotLen = 0;
|
|
696
782
|
try {
|
|
@@ -714,8 +800,8 @@ ${transcript || "(The agent has not spoken yet)"}
|
|
|
714
800
|
|
|
715
801
|
Choose "speak" and provide your next utterance, or choose "hangup" if the conversation has naturally concluded.`;
|
|
716
802
|
|
|
717
|
-
const raw = await _generate(this._client, prompt,
|
|
718
|
-
const action = JSON.parse(raw)
|
|
803
|
+
const raw = await _generate(this._client, prompt, z.toJSONSchema(_roleplayActionSchema));
|
|
804
|
+
const action = _roleplayActionSchema.parse(JSON.parse(raw));
|
|
719
805
|
|
|
720
806
|
if (action.action === "hangup") {
|
|
721
807
|
this._logger.info("(Roleplay Session) [caller hangs up]");
|
|
@@ -800,10 +886,11 @@ Choose "speak" and provide your next utterance, or choose "hangup" if the conver
|
|
|
800
886
|
cloned._onSessionEnd = this._onSessionEnd;
|
|
801
887
|
cloned._onEscalate = this._onEscalate;
|
|
802
888
|
cloned._onDtmf = this._onDtmf;
|
|
889
|
+
cloned._onOutboundFailed = this._onOutboundFailed;
|
|
803
890
|
return cloned;
|
|
804
891
|
}
|
|
805
892
|
|
|
806
|
-
|
|
893
|
+
async _serveCampaign(healthCtx: HealthContext, campaignCode: string): Promise<void> {
|
|
807
894
|
try {
|
|
808
895
|
const campaignUrl = new URL(`v1/campaigns/${campaignCode}`, this._client.getHttpBase());
|
|
809
896
|
const campaignResponse = await fetchOrThrow(campaignUrl, {
|
|
@@ -898,11 +985,7 @@ Choose "speak" and provide your next utterance, or choose "hangup" if the conver
|
|
|
898
985
|
}
|
|
899
986
|
|
|
900
987
|
/** @deprecated Use {@link callPhone} instead. */
|
|
901
|
-
async outboundPhone(
|
|
902
|
-
fromNumber: string | undefined,
|
|
903
|
-
toNumber: string,
|
|
904
|
-
variables: Record<string, any> = {},
|
|
905
|
-
) {
|
|
988
|
+
async outboundPhone(fromNumber: string, toNumber: string, variables: Record<string, any> = {}) {
|
|
906
989
|
return this.callPhone(fromNumber, toNumber, variables);
|
|
907
990
|
}
|
|
908
991
|
}
|