signalwire-agents 0.1.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/CLAUDE.md +112 -0
- package/README.md +417 -0
- package/dist/AgentBase.d.ts +481 -0
- package/dist/AgentBase.js +1155 -0
- package/dist/AgentBase.js.map +1 -0
- package/dist/AgentServer.d.ts +60 -0
- package/dist/AgentServer.js +128 -0
- package/dist/AgentServer.js.map +1 -0
- package/dist/AuthHandler.d.ts +56 -0
- package/dist/AuthHandler.js +119 -0
- package/dist/AuthHandler.js.map +1 -0
- package/dist/ConfigLoader.d.ts +68 -0
- package/dist/ConfigLoader.js +150 -0
- package/dist/ConfigLoader.js.map +1 -0
- package/dist/ContextBuilder.d.ts +431 -0
- package/dist/ContextBuilder.js +751 -0
- package/dist/ContextBuilder.js.map +1 -0
- package/dist/DataMap.d.ts +179 -0
- package/dist/DataMap.js +321 -0
- package/dist/DataMap.js.map +1 -0
- package/dist/Logger.d.ts +91 -0
- package/dist/Logger.js +191 -0
- package/dist/Logger.js.map +1 -0
- package/dist/PomBuilder.d.ts +144 -0
- package/dist/PomBuilder.js +249 -0
- package/dist/PomBuilder.js.map +1 -0
- package/dist/PromptManager.d.ts +85 -0
- package/dist/PromptManager.js +108 -0
- package/dist/PromptManager.js.map +1 -0
- package/dist/SchemaUtils.d.ts +42 -0
- package/dist/SchemaUtils.js +148 -0
- package/dist/SchemaUtils.js.map +1 -0
- package/dist/ServerlessAdapter.d.ts +100 -0
- package/dist/ServerlessAdapter.js +166 -0
- package/dist/ServerlessAdapter.js.map +1 -0
- package/dist/SessionManager.d.ts +86 -0
- package/dist/SessionManager.js +155 -0
- package/dist/SessionManager.js.map +1 -0
- package/dist/SslConfig.d.ts +77 -0
- package/dist/SslConfig.js +100 -0
- package/dist/SslConfig.js.map +1 -0
- package/dist/SwaigFunction.d.ts +88 -0
- package/dist/SwaigFunction.js +125 -0
- package/dist/SwaigFunction.js.map +1 -0
- package/dist/SwaigFunctionResult.d.ts +442 -0
- package/dist/SwaigFunctionResult.js +735 -0
- package/dist/SwaigFunctionResult.js.map +1 -0
- package/dist/SwmlBuilder.d.ts +37 -0
- package/dist/SwmlBuilder.js +55 -0
- package/dist/SwmlBuilder.js.map +1 -0
- package/dist/cli/agent-loader.d.ts +22 -0
- package/dist/cli/agent-loader.js +106 -0
- package/dist/cli/agent-loader.js.map +1 -0
- package/dist/cli/mock-data.d.ts +37 -0
- package/dist/cli/mock-data.js +79 -0
- package/dist/cli/mock-data.js.map +1 -0
- package/dist/cli/swaig-test.d.ts +32 -0
- package/dist/cli/swaig-test.js +405 -0
- package/dist/cli/swaig-test.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/prefabs/ConciergeAgent.d.ts +64 -0
- package/dist/prefabs/ConciergeAgent.js +188 -0
- package/dist/prefabs/ConciergeAgent.js.map +1 -0
- package/dist/prefabs/FAQBotAgent.d.ts +68 -0
- package/dist/prefabs/FAQBotAgent.js +206 -0
- package/dist/prefabs/FAQBotAgent.js.map +1 -0
- package/dist/prefabs/InfoGathererAgent.d.ts +73 -0
- package/dist/prefabs/InfoGathererAgent.js +202 -0
- package/dist/prefabs/InfoGathererAgent.js.map +1 -0
- package/dist/prefabs/ReceptionistAgent.d.ts +65 -0
- package/dist/prefabs/ReceptionistAgent.js +205 -0
- package/dist/prefabs/ReceptionistAgent.js.map +1 -0
- package/dist/prefabs/SurveyAgent.d.ts +84 -0
- package/dist/prefabs/SurveyAgent.js +324 -0
- package/dist/prefabs/SurveyAgent.js.map +1 -0
- package/dist/prefabs/index.d.ts +13 -0
- package/dist/prefabs/index.js +14 -0
- package/dist/prefabs/index.js.map +1 -0
- package/dist/skills/SkillBase.d.ts +134 -0
- package/dist/skills/SkillBase.js +105 -0
- package/dist/skills/SkillBase.js.map +1 -0
- package/dist/skills/SkillManager.d.ts +83 -0
- package/dist/skills/SkillManager.js +159 -0
- package/dist/skills/SkillManager.js.map +1 -0
- package/dist/skills/SkillRegistry.d.ts +105 -0
- package/dist/skills/SkillRegistry.js +195 -0
- package/dist/skills/SkillRegistry.js.map +1 -0
- package/dist/skills/builtin/api_ninjas_trivia.d.ts +35 -0
- package/dist/skills/builtin/api_ninjas_trivia.js +161 -0
- package/dist/skills/builtin/api_ninjas_trivia.js.map +1 -0
- package/dist/skills/builtin/claude_skills.d.ts +34 -0
- package/dist/skills/builtin/claude_skills.js +156 -0
- package/dist/skills/builtin/claude_skills.js.map +1 -0
- package/dist/skills/builtin/custom_skills.d.ts +59 -0
- package/dist/skills/builtin/custom_skills.js +231 -0
- package/dist/skills/builtin/custom_skills.js.map +1 -0
- package/dist/skills/builtin/datasphere.d.ts +34 -0
- package/dist/skills/builtin/datasphere.js +163 -0
- package/dist/skills/builtin/datasphere.js.map +1 -0
- package/dist/skills/builtin/datasphere_serverless.d.ts +54 -0
- package/dist/skills/builtin/datasphere_serverless.js +155 -0
- package/dist/skills/builtin/datasphere_serverless.js.map +1 -0
- package/dist/skills/builtin/datetime.d.ts +31 -0
- package/dist/skills/builtin/datetime.js +95 -0
- package/dist/skills/builtin/datetime.js.map +1 -0
- package/dist/skills/builtin/google_maps.d.ts +33 -0
- package/dist/skills/builtin/google_maps.js +233 -0
- package/dist/skills/builtin/google_maps.js.map +1 -0
- package/dist/skills/builtin/index.d.ts +28 -0
- package/dist/skills/builtin/index.js +75 -0
- package/dist/skills/builtin/index.js.map +1 -0
- package/dist/skills/builtin/info_gatherer.d.ts +52 -0
- package/dist/skills/builtin/info_gatherer.js +237 -0
- package/dist/skills/builtin/info_gatherer.js.map +1 -0
- package/dist/skills/builtin/joke.d.ts +32 -0
- package/dist/skills/builtin/joke.js +142 -0
- package/dist/skills/builtin/joke.js.map +1 -0
- package/dist/skills/builtin/math.d.ts +33 -0
- package/dist/skills/builtin/math.js +120 -0
- package/dist/skills/builtin/math.js.map +1 -0
- package/dist/skills/builtin/mcp_gateway.d.ts +35 -0
- package/dist/skills/builtin/mcp_gateway.js +85 -0
- package/dist/skills/builtin/mcp_gateway.js.map +1 -0
- package/dist/skills/builtin/native_vector_search.d.ts +44 -0
- package/dist/skills/builtin/native_vector_search.js +248 -0
- package/dist/skills/builtin/native_vector_search.js.map +1 -0
- package/dist/skills/builtin/play_background_file.d.ts +34 -0
- package/dist/skills/builtin/play_background_file.js +135 -0
- package/dist/skills/builtin/play_background_file.js.map +1 -0
- package/dist/skills/builtin/spider.d.ts +34 -0
- package/dist/skills/builtin/spider.js +164 -0
- package/dist/skills/builtin/spider.js.map +1 -0
- package/dist/skills/builtin/swml_transfer.d.ts +44 -0
- package/dist/skills/builtin/swml_transfer.js +204 -0
- package/dist/skills/builtin/swml_transfer.js.map +1 -0
- package/dist/skills/builtin/weather_api.d.ts +34 -0
- package/dist/skills/builtin/weather_api.js +136 -0
- package/dist/skills/builtin/weather_api.js.map +1 -0
- package/dist/skills/builtin/web_search.d.ts +34 -0
- package/dist/skills/builtin/web_search.js +141 -0
- package/dist/skills/builtin/web_search.js.map +1 -0
- package/dist/skills/builtin/wikipedia_search.d.ts +34 -0
- package/dist/skills/builtin/wikipedia_search.js +166 -0
- package/dist/skills/builtin/wikipedia_search.js.map +1 -0
- package/dist/skills/index.d.ts +11 -0
- package/dist/skills/index.js +10 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/types.d.ts +85 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/docs/agent-guide.md +869 -0
- package/docs/api-reference.md +2419 -0
- package/docs/architecture.md +843 -0
- package/docs/cli-guide.md +579 -0
- package/docs/configuration.md +422 -0
- package/docs/contexts-guide.md +787 -0
- package/docs/datamap-guide.md +1141 -0
- package/docs/prefabs-guide.md +830 -0
- package/docs/security.md +436 -0
- package/docs/serverless-guide.md +513 -0
- package/docs/skills-guide.md +845 -0
- package/docs/swaig-reference.md +1627 -0
- package/examples/call-flow.ts +60 -0
- package/examples/contexts-steps.ts +62 -0
- package/examples/datamap-tools.ts +52 -0
- package/examples/dynamic-config.ts +51 -0
- package/examples/multi-agent.ts +67 -0
- package/examples/pom-prompt.ts +68 -0
- package/examples/simple-agent.ts +33 -0
- package/package.json +35 -0
- package/src/AgentBase.ts +1279 -0
- package/src/AgentServer.ts +149 -0
- package/src/AuthHandler.ts +141 -0
- package/src/ConfigLoader.ts +165 -0
- package/src/ContextBuilder.ts +796 -0
- package/src/DataMap.ts +365 -0
- package/src/Logger.ts +197 -0
- package/src/PomBuilder.ts +298 -0
- package/src/PromptManager.ts +133 -0
- package/src/SchemaUtils.ts +170 -0
- package/src/ServerlessAdapter.ts +218 -0
- package/src/SessionManager.ts +161 -0
- package/src/SslConfig.ts +118 -0
- package/src/SwaigFunction.ts +168 -0
- package/src/SwaigFunctionResult.ts +864 -0
- package/src/SwmlBuilder.ts +62 -0
- package/src/cli/agent-loader.ts +122 -0
- package/src/cli/mock-data.ts +110 -0
- package/src/cli/swaig-test.ts +440 -0
- package/src/index.ts +99 -0
- package/src/prefabs/ConciergeAgent.ts +251 -0
- package/src/prefabs/FAQBotAgent.ts +263 -0
- package/src/prefabs/InfoGathererAgent.ts +276 -0
- package/src/prefabs/ReceptionistAgent.ts +277 -0
- package/src/prefabs/SurveyAgent.ts +413 -0
- package/src/prefabs/index.ts +23 -0
- package/src/skills/SkillBase.ts +184 -0
- package/src/skills/SkillManager.ts +177 -0
- package/src/skills/SkillRegistry.ts +231 -0
- package/src/skills/builtin/api_ninjas_trivia.ts +230 -0
- package/src/skills/builtin/claude_skills.ts +239 -0
- package/src/skills/builtin/custom_skills.ts +326 -0
- package/src/skills/builtin/datasphere.ts +225 -0
- package/src/skills/builtin/datasphere_serverless.ts +194 -0
- package/src/skills/builtin/datetime.ts +115 -0
- package/src/skills/builtin/google_maps.ts +347 -0
- package/src/skills/builtin/index.ts +79 -0
- package/src/skills/builtin/info_gatherer.ts +331 -0
- package/src/skills/builtin/joke.ts +180 -0
- package/src/skills/builtin/math.ts +150 -0
- package/src/skills/builtin/mcp_gateway.ts +99 -0
- package/src/skills/builtin/native_vector_search.ts +327 -0
- package/src/skills/builtin/play_background_file.ts +189 -0
- package/src/skills/builtin/spider.ts +232 -0
- package/src/skills/builtin/swml_transfer.ts +285 -0
- package/src/skills/builtin/weather_api.ts +193 -0
- package/src/skills/builtin/web_search.ts +212 -0
- package/src/skills/builtin/wikipedia_search.ts +248 -0
- package/src/skills/index.ts +12 -0
- package/src/types.ts +99 -0
- package/tests/AgentBase.test.ts +693 -0
- package/tests/AgentServer.test.ts +102 -0
- package/tests/AuthHandler.test.ts +121 -0
- package/tests/ConfigLoader.test.ts +121 -0
- package/tests/DataMap.test.ts +286 -0
- package/tests/Logger.test.ts +218 -0
- package/tests/PomBuilder.test.ts +164 -0
- package/tests/ProxyDetection.test.ts +185 -0
- package/tests/SchemaUtils.test.ts +106 -0
- package/tests/ServerlessAdapter.test.ts +200 -0
- package/tests/SessionManager.test.ts +139 -0
- package/tests/SslConfig.test.ts +97 -0
- package/tests/SwaigFunctionResult.test.ts +298 -0
- package/tests/cli/mock-data.test.ts +94 -0
- package/tests/cli/swaig-test.test.ts +96 -0
- package/tests/prefabs/prefabs.test.ts +854 -0
- package/tests/setup.ts +6 -0
- package/tests/skills/SkillBase.test.ts +118 -0
- package/tests/skills/SkillManager.test.ts +164 -0
- package/tests/skills/SkillRegistry.test.ts +124 -0
- package/tests/skills/builtin.test.ts +924 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConciergeAgent - A prefab agent that provides multi-department routing
|
|
3
|
+
* with a knowledge base of department info, hours, and transfer capabilities.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { AgentBase } from '../AgentBase.js';
|
|
7
|
+
import { SwaigFunctionResult } from '../SwaigFunctionResult.js';
|
|
8
|
+
import type { AgentOptions } from '../types.js';
|
|
9
|
+
|
|
10
|
+
// ── Config types ────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
export interface Department {
|
|
13
|
+
/** Department name (e.g. "Sales", "Support"). */
|
|
14
|
+
name: string;
|
|
15
|
+
/** Description of what this department handles. */
|
|
16
|
+
description: string;
|
|
17
|
+
/** Phone number or SIP address for transfers. */
|
|
18
|
+
transferNumber?: string;
|
|
19
|
+
/** Keywords that help route callers to this department. */
|
|
20
|
+
keywords?: string[];
|
|
21
|
+
/** Human-readable hours of operation (e.g. "Mon-Fri 9am-5pm EST"). */
|
|
22
|
+
hoursOfOperation?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ConciergeConfig {
|
|
26
|
+
/** Agent display name. */
|
|
27
|
+
name?: string;
|
|
28
|
+
/** List of departments available for routing. */
|
|
29
|
+
departments: Department[];
|
|
30
|
+
/** Company name used in greetings and prompts. */
|
|
31
|
+
companyName?: string;
|
|
32
|
+
/** General company information the agent can share. */
|
|
33
|
+
generalInfo?: string;
|
|
34
|
+
/** Message spoken when a department is closed or unavailable. */
|
|
35
|
+
afterHoursMessage?: string;
|
|
36
|
+
/** Additional AgentBase options forwarded to super(). */
|
|
37
|
+
agentOptions?: Partial<AgentOptions>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Agent ───────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/** Prefab agent that provides multi-department routing with a knowledge base of department info, hours, and transfer capabilities. */
|
|
43
|
+
export class ConciergeAgent extends AgentBase {
|
|
44
|
+
private departments: Department[];
|
|
45
|
+
private companyName: string;
|
|
46
|
+
private generalInfo: string;
|
|
47
|
+
private afterHoursMessage: string;
|
|
48
|
+
|
|
49
|
+
/** Declarative prompt sections merged by AgentBase constructor. */
|
|
50
|
+
static override PROMPT_SECTIONS = [
|
|
51
|
+
{
|
|
52
|
+
title: 'Role',
|
|
53
|
+
body: 'You are a professional concierge agent. Help callers find the right department, provide information about available services, and transfer calls when requested.',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
title: 'Rules',
|
|
57
|
+
bullets: [
|
|
58
|
+
'Greet the caller warmly and ask how you can help.',
|
|
59
|
+
'Use list_departments to describe available departments if the caller is unsure where to go.',
|
|
60
|
+
'Use get_department_info to provide detailed information about a specific department.',
|
|
61
|
+
'Use transfer_to_department to connect the caller to the right department.',
|
|
62
|
+
'If a department has hours of operation listed, mention them before transferring.',
|
|
63
|
+
'If a department does not have a transfer number, inform the caller and offer alternatives.',
|
|
64
|
+
'Be polite, professional, and efficient.',
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Create a ConciergeAgent with the specified departments and company info.
|
|
71
|
+
* @param config - Configuration including departments, company name, and after-hours message.
|
|
72
|
+
*/
|
|
73
|
+
constructor(config: ConciergeConfig) {
|
|
74
|
+
const agentName = config.name ?? 'Concierge';
|
|
75
|
+
super({
|
|
76
|
+
name: agentName,
|
|
77
|
+
...config.agentOptions,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
this.departments = config.departments;
|
|
81
|
+
this.companyName = config.companyName ?? 'our company';
|
|
82
|
+
this.generalInfo = config.generalInfo ?? '';
|
|
83
|
+
this.afterHoursMessage = config.afterHoursMessage ?? 'This department is currently closed. Please try again during business hours.';
|
|
84
|
+
|
|
85
|
+
// Company intro section
|
|
86
|
+
let introBody = `You are the concierge for ${this.companyName}.`;
|
|
87
|
+
if (this.generalInfo) {
|
|
88
|
+
introBody += ` Company information: ${this.generalInfo}`;
|
|
89
|
+
}
|
|
90
|
+
this.promptAddSection('Company', { body: introBody });
|
|
91
|
+
|
|
92
|
+
// Build department overview section
|
|
93
|
+
const deptBullets = this.departments.map((dept) => {
|
|
94
|
+
let desc = `${dept.name}: ${dept.description}`;
|
|
95
|
+
if (dept.hoursOfOperation) desc += ` (Hours: ${dept.hoursOfOperation})`;
|
|
96
|
+
if (dept.transferNumber) desc += ' [transfer available]';
|
|
97
|
+
return desc;
|
|
98
|
+
});
|
|
99
|
+
this.promptAddSection('Available Departments', { bullets: deptBullets });
|
|
100
|
+
|
|
101
|
+
// Speech recognition hints from department names and keywords
|
|
102
|
+
const hints: string[] = this.departments.map((d) => d.name);
|
|
103
|
+
for (const dept of this.departments) {
|
|
104
|
+
if (dept.keywords) hints.push(...dept.keywords);
|
|
105
|
+
}
|
|
106
|
+
this.addHints(hints);
|
|
107
|
+
|
|
108
|
+
// Register tools after all fields are initialized
|
|
109
|
+
this.defineTools();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Department helpers ────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
private findDepartment(name: string): Department | undefined {
|
|
115
|
+
const normalized = name.toLowerCase().trim();
|
|
116
|
+
return this.departments.find(
|
|
117
|
+
(d) =>
|
|
118
|
+
d.name.toLowerCase() === normalized ||
|
|
119
|
+
d.keywords?.some((k) => k.toLowerCase() === normalized),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── Tool registration ─────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
/** Register the list_departments, get_department_info, and transfer_to_department SWAIG tools. */
|
|
126
|
+
protected override defineTools(): void {
|
|
127
|
+
// Tool: list_departments
|
|
128
|
+
this.defineTool({
|
|
129
|
+
name: 'list_departments',
|
|
130
|
+
description: `List all available departments at ${this.companyName} with their descriptions and hours of operation.`,
|
|
131
|
+
parameters: {
|
|
132
|
+
type: 'object',
|
|
133
|
+
properties: {},
|
|
134
|
+
},
|
|
135
|
+
handler: () => {
|
|
136
|
+
if (this.departments.length === 0) {
|
|
137
|
+
return new SwaigFunctionResult('No departments are currently configured.');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const lines = this.departments.map((dept) => {
|
|
141
|
+
let line = `${dept.name}: ${dept.description}`;
|
|
142
|
+
if (dept.hoursOfOperation) line += ` | Hours: ${dept.hoursOfOperation}`;
|
|
143
|
+
if (!dept.transferNumber) line += ' | (no direct transfer available)';
|
|
144
|
+
return line;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return new SwaigFunctionResult(
|
|
148
|
+
`Available departments at ${this.companyName}:\n${lines.join('\n')}`,
|
|
149
|
+
);
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Tool: get_department_info
|
|
154
|
+
this.defineTool({
|
|
155
|
+
name: 'get_department_info',
|
|
156
|
+
description: 'Get detailed information about a specific department including description, hours of operation, and transfer availability.',
|
|
157
|
+
parameters: {
|
|
158
|
+
type: 'object',
|
|
159
|
+
properties: {
|
|
160
|
+
department_name: {
|
|
161
|
+
type: 'string',
|
|
162
|
+
description: 'The name of the department to look up.',
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
required: ['department_name'],
|
|
166
|
+
},
|
|
167
|
+
handler: (_args: Record<string, unknown>) => {
|
|
168
|
+
const deptName = _args['department_name'] as string;
|
|
169
|
+
if (!deptName) {
|
|
170
|
+
return new SwaigFunctionResult('Please provide a department name.');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const dept = this.findDepartment(deptName);
|
|
174
|
+
if (!dept) {
|
|
175
|
+
const available = this.departments.map((d) => d.name).join(', ');
|
|
176
|
+
return new SwaigFunctionResult(
|
|
177
|
+
`Department "${deptName}" not found. Available departments: ${available}`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let info = `Department: ${dept.name}\nDescription: ${dept.description}`;
|
|
182
|
+
if (dept.hoursOfOperation) info += `\nHours: ${dept.hoursOfOperation}`;
|
|
183
|
+
if (dept.transferNumber) {
|
|
184
|
+
info += '\nTransfer: available';
|
|
185
|
+
} else {
|
|
186
|
+
info += '\nTransfer: not available (no phone number configured)';
|
|
187
|
+
}
|
|
188
|
+
if (dept.keywords && dept.keywords.length > 0) {
|
|
189
|
+
info += `\nRelated topics: ${dept.keywords.join(', ')}`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return new SwaigFunctionResult(info);
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Tool: transfer_to_department
|
|
197
|
+
this.defineTool({
|
|
198
|
+
name: 'transfer_to_department',
|
|
199
|
+
description: 'Transfer the caller to a specific department. Only works if the department has a transfer number configured.',
|
|
200
|
+
parameters: {
|
|
201
|
+
type: 'object',
|
|
202
|
+
properties: {
|
|
203
|
+
department_name: {
|
|
204
|
+
type: 'string',
|
|
205
|
+
description: 'The name of the department to transfer to.',
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
required: ['department_name'],
|
|
209
|
+
},
|
|
210
|
+
handler: (_args: Record<string, unknown>) => {
|
|
211
|
+
const deptName = _args['department_name'] as string;
|
|
212
|
+
if (!deptName) {
|
|
213
|
+
return new SwaigFunctionResult('Please provide a department name.');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const dept = this.findDepartment(deptName);
|
|
217
|
+
if (!dept) {
|
|
218
|
+
const available = this.departments.map((d) => d.name).join(', ');
|
|
219
|
+
return new SwaigFunctionResult(
|
|
220
|
+
`Department "${deptName}" not found. Available departments: ${available}`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!dept.transferNumber) {
|
|
225
|
+
return new SwaigFunctionResult(
|
|
226
|
+
`The ${dept.name} department does not have a direct transfer number. ${this.afterHoursMessage}`,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const result = new SwaigFunctionResult(
|
|
231
|
+
`Transferring you to the ${dept.name} department now.`,
|
|
232
|
+
);
|
|
233
|
+
result.connect(dept.transferNumber);
|
|
234
|
+
return result;
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ── Factory function ────────────────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Factory function that creates and returns a new ConciergeAgent.
|
|
244
|
+
* @param config - Configuration for the concierge agent.
|
|
245
|
+
* @returns A configured ConciergeAgent instance.
|
|
246
|
+
*/
|
|
247
|
+
export function createConciergeAgent(config: ConciergeConfig): ConciergeAgent {
|
|
248
|
+
return new ConciergeAgent(config);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export default ConciergeAgent;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FAQBotAgent - A prefab agent that answers frequently asked questions using
|
|
3
|
+
* keyword/word-overlap matching with configurable similarity thresholds
|
|
4
|
+
* and optional escalation to a live agent.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { AgentBase } from '../AgentBase.js';
|
|
8
|
+
import { SwaigFunctionResult } from '../SwaigFunctionResult.js';
|
|
9
|
+
import type { AgentOptions } from '../types.js';
|
|
10
|
+
|
|
11
|
+
// ── Config types ────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export interface FAQEntry {
|
|
14
|
+
/** The representative question text. */
|
|
15
|
+
question: string;
|
|
16
|
+
/** The answer to provide when this FAQ matches. */
|
|
17
|
+
answer: string;
|
|
18
|
+
/** Additional keywords to boost matching accuracy. */
|
|
19
|
+
keywords?: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface FAQBotConfig {
|
|
23
|
+
/** Agent display name. */
|
|
24
|
+
name?: string;
|
|
25
|
+
/** List of FAQ entries for the knowledge base. */
|
|
26
|
+
faqs: FAQEntry[];
|
|
27
|
+
/** Minimum match score (0-1) for an FAQ to be considered a match. Default 0.5. */
|
|
28
|
+
threshold?: number;
|
|
29
|
+
/** Message spoken when no FAQ matches the query. */
|
|
30
|
+
escalationMessage?: string;
|
|
31
|
+
/** Phone number to transfer to on escalation. If not set, escalate tool is not registered. */
|
|
32
|
+
escalationNumber?: string;
|
|
33
|
+
/** Additional AgentBase options forwarded to super(). */
|
|
34
|
+
agentOptions?: Partial<AgentOptions>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── Agent ───────────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
/** Prefab agent that answers frequently asked questions using keyword matching with optional escalation to a live agent. */
|
|
40
|
+
export class FAQBotAgent extends AgentBase {
|
|
41
|
+
private faqs: FAQEntry[];
|
|
42
|
+
private threshold: number;
|
|
43
|
+
private escalationMessage: string;
|
|
44
|
+
private escalationNumber: string | null;
|
|
45
|
+
|
|
46
|
+
/** Declarative prompt sections merged by AgentBase constructor. */
|
|
47
|
+
static override PROMPT_SECTIONS = [
|
|
48
|
+
{
|
|
49
|
+
title: 'Role',
|
|
50
|
+
body: 'You are a helpful FAQ assistant. Answer the caller\'s questions by searching the knowledge base. If you cannot find a satisfactory answer, offer to escalate to a human agent.',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
title: 'Rules',
|
|
54
|
+
bullets: [
|
|
55
|
+
'Always use the search_faq tool to find answers. Do not make up answers.',
|
|
56
|
+
'If the search returns a good match, relay the answer naturally to the caller.',
|
|
57
|
+
'If the match score is low or no match is found, let the caller know and offer to escalate.',
|
|
58
|
+
'Be concise and helpful in your responses.',
|
|
59
|
+
'If the caller asks a follow-up, search again with the new query.',
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Create a FAQBotAgent with the specified FAQ entries and matching threshold.
|
|
66
|
+
* @param config - Configuration including FAQ entries, threshold, and escalation settings.
|
|
67
|
+
*/
|
|
68
|
+
constructor(config: FAQBotConfig) {
|
|
69
|
+
const agentName = config.name ?? 'FAQBot';
|
|
70
|
+
super({
|
|
71
|
+
name: agentName,
|
|
72
|
+
...config.agentOptions,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
this.faqs = config.faqs;
|
|
76
|
+
this.threshold = config.threshold ?? 0.5;
|
|
77
|
+
this.escalationMessage = config.escalationMessage ?? 'I\'m sorry, I couldn\'t find an answer to your question. Let me transfer you to someone who can help.';
|
|
78
|
+
this.escalationNumber = config.escalationNumber ?? null;
|
|
79
|
+
|
|
80
|
+
// Build FAQ topics section for the prompt
|
|
81
|
+
const topicBullets = this.faqs.map((faq) => {
|
|
82
|
+
const kw = faq.keywords ? ` (keywords: ${faq.keywords.join(', ')})` : '';
|
|
83
|
+
return `${faq.question}${kw}`;
|
|
84
|
+
});
|
|
85
|
+
this.promptAddSection('Available FAQ Topics', { bullets: topicBullets });
|
|
86
|
+
|
|
87
|
+
// Add hints from FAQ keywords for speech recognition
|
|
88
|
+
const allKeywords: string[] = [];
|
|
89
|
+
for (const faq of this.faqs) {
|
|
90
|
+
if (faq.keywords) allKeywords.push(...faq.keywords);
|
|
91
|
+
}
|
|
92
|
+
if (allKeywords.length > 0) {
|
|
93
|
+
this.addHints(allKeywords);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Register tools after all fields are initialized
|
|
97
|
+
this.defineTools();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Matching engine ───────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Compute a word-overlap similarity score between a query and an FAQ entry.
|
|
104
|
+
* Combines question text similarity and keyword overlap.
|
|
105
|
+
* Returns a value between 0 and 1.
|
|
106
|
+
*/
|
|
107
|
+
private computeMatchScore(query: string, faq: FAQEntry): number {
|
|
108
|
+
const queryWords = this.tokenize(query);
|
|
109
|
+
if (queryWords.length === 0) return 0;
|
|
110
|
+
|
|
111
|
+
// Score against the question text
|
|
112
|
+
const questionWords = this.tokenize(faq.question);
|
|
113
|
+
const questionOverlap = this.wordOverlap(queryWords, questionWords);
|
|
114
|
+
|
|
115
|
+
// Score against keywords
|
|
116
|
+
let keywordScore = 0;
|
|
117
|
+
if (faq.keywords && faq.keywords.length > 0) {
|
|
118
|
+
const keywordTokens = faq.keywords.map((k) => k.toLowerCase().trim());
|
|
119
|
+
const queryLower = query.toLowerCase();
|
|
120
|
+
let keywordHits = 0;
|
|
121
|
+
for (const kw of keywordTokens) {
|
|
122
|
+
if (queryLower.includes(kw)) {
|
|
123
|
+
keywordHits++;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
keywordScore = keywordTokens.length > 0 ? keywordHits / keywordTokens.length : 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Weighted combination: 60% question overlap, 40% keyword score
|
|
130
|
+
return faq.keywords && faq.keywords.length > 0
|
|
131
|
+
? questionOverlap * 0.6 + keywordScore * 0.4
|
|
132
|
+
: questionOverlap;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private tokenize(text: string): string[] {
|
|
136
|
+
return text
|
|
137
|
+
.toLowerCase()
|
|
138
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
139
|
+
.split(/\s+/)
|
|
140
|
+
.filter((w) => w.length > 1 && !STOP_WORDS.has(w));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private wordOverlap(queryWords: string[], targetWords: string[]): number {
|
|
144
|
+
if (queryWords.length === 0 || targetWords.length === 0) return 0;
|
|
145
|
+
const targetSet = new Set(targetWords);
|
|
146
|
+
let matches = 0;
|
|
147
|
+
for (const word of queryWords) {
|
|
148
|
+
if (targetSet.has(word)) matches++;
|
|
149
|
+
}
|
|
150
|
+
// Normalize by the smaller set to be lenient
|
|
151
|
+
const divisor = Math.min(queryWords.length, targetWords.length);
|
|
152
|
+
return divisor > 0 ? matches / divisor : 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── Tool registration ─────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
/** Register the search_faq and optional escalate SWAIG tools. */
|
|
158
|
+
protected override defineTools(): void {
|
|
159
|
+
// Tool: search_faq
|
|
160
|
+
this.defineTool({
|
|
161
|
+
name: 'search_faq',
|
|
162
|
+
description: 'Search the FAQ knowledge base for an answer to the caller\'s question. Returns the best matching FAQ entry with a confidence score.',
|
|
163
|
+
parameters: {
|
|
164
|
+
type: 'object',
|
|
165
|
+
properties: {
|
|
166
|
+
query: {
|
|
167
|
+
type: 'string',
|
|
168
|
+
description: 'The caller\'s question or search query.',
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
required: ['query'],
|
|
172
|
+
},
|
|
173
|
+
handler: (_args: Record<string, unknown>) => {
|
|
174
|
+
const query = _args['query'] as string;
|
|
175
|
+
if (!query) {
|
|
176
|
+
return new SwaigFunctionResult('A query is required to search the FAQ.');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Score all FAQs
|
|
180
|
+
const scored = this.faqs.map((faq) => ({
|
|
181
|
+
faq,
|
|
182
|
+
score: this.computeMatchScore(query, faq),
|
|
183
|
+
}));
|
|
184
|
+
|
|
185
|
+
// Sort by score descending
|
|
186
|
+
scored.sort((a, b) => b.score - a.score);
|
|
187
|
+
|
|
188
|
+
const best = scored[0];
|
|
189
|
+
|
|
190
|
+
if (!best || best.score < this.threshold) {
|
|
191
|
+
return new SwaigFunctionResult(
|
|
192
|
+
`No FAQ matched the query "${query}" with sufficient confidence (best score: ${best ? best.score.toFixed(2) : '0.00'}, threshold: ${this.threshold.toFixed(2)}). ${this.escalationMessage}`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Return the best match
|
|
197
|
+
let response = `FAQ Match (confidence: ${best.score.toFixed(2)}): Q: "${best.faq.question}" A: ${best.faq.answer}`;
|
|
198
|
+
|
|
199
|
+
// If there are close runner-ups, mention them
|
|
200
|
+
const runnerUps = scored.filter((s, i) => i > 0 && s.score >= this.threshold);
|
|
201
|
+
if (runnerUps.length > 0) {
|
|
202
|
+
const alsoStr = runnerUps
|
|
203
|
+
.slice(0, 2)
|
|
204
|
+
.map((s) => `"${s.faq.question}" (${s.score.toFixed(2)})`)
|
|
205
|
+
.join(', ');
|
|
206
|
+
response += ` Also related: ${alsoStr}`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return new SwaigFunctionResult(response);
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Tool: escalate (only if escalation number is configured)
|
|
214
|
+
if (this.escalationNumber) {
|
|
215
|
+
this.defineTool({
|
|
216
|
+
name: 'escalate',
|
|
217
|
+
description: 'Transfer the caller to a live agent when the FAQ knowledge base cannot answer their question.',
|
|
218
|
+
parameters: {
|
|
219
|
+
type: 'object',
|
|
220
|
+
properties: {
|
|
221
|
+
reason: {
|
|
222
|
+
type: 'string',
|
|
223
|
+
description: 'The reason for escalation.',
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
handler: (_args: Record<string, unknown>) => {
|
|
228
|
+
const reason = (_args['reason'] as string) || 'Caller needs assistance beyond FAQ';
|
|
229
|
+
const result = new SwaigFunctionResult(
|
|
230
|
+
`Transferring caller to a live agent. Reason: ${reason}`,
|
|
231
|
+
);
|
|
232
|
+
result.connect(this.escalationNumber!);
|
|
233
|
+
return result;
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ── Common English stop words filtered from matching ────────────────────────
|
|
241
|
+
|
|
242
|
+
const STOP_WORDS = new Set([
|
|
243
|
+
'a', 'an', 'the', 'is', 'it', 'to', 'in', 'of', 'and', 'or', 'for',
|
|
244
|
+
'on', 'at', 'by', 'do', 'if', 'my', 'so', 'up', 'am', 'as', 'be',
|
|
245
|
+
'he', 'me', 'we', 'no', 'not', 'are', 'was', 'has', 'had', 'how',
|
|
246
|
+
'its', 'can', 'did', 'get', 'but', 'you', 'her', 'him', 'his', 'she',
|
|
247
|
+
'our', 'who', 'this', 'that', 'what', 'with', 'have', 'from', 'will',
|
|
248
|
+
'your', 'they', 'them', 'been', 'does', 'just', 'than', 'when', 'which',
|
|
249
|
+
'would', 'could', 'should', 'about', 'their', 'there', 'these', 'those',
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
// ── Factory function ────────────────────────────────────────────────────────
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Factory function that creates and returns a new FAQBotAgent.
|
|
256
|
+
* @param config - Configuration for the FAQ bot agent.
|
|
257
|
+
* @returns A configured FAQBotAgent instance.
|
|
258
|
+
*/
|
|
259
|
+
export function createFAQBotAgent(config: FAQBotConfig): FAQBotAgent {
|
|
260
|
+
return new FAQBotAgent(config);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export default FAQBotAgent;
|