@fonoster/autopilot 0.8.52 → 0.8.59
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/Autopilot.js +1 -1
- package/dist/handleVoiceRequest.js +4 -4
- package/dist/models/AbstractLanguageModel.js +34 -15
- package/dist/{createLanguageModel.d.ts → models/createLanguageModel.d.ts} +2 -2
- package/dist/{createLanguageModel.js → models/createLanguageModel.js} +7 -12
- package/dist/models/evaluations/createTestTextSimilarity.d.ts +6 -0
- package/dist/models/evaluations/createTestTextSimilarity.js +43 -0
- package/dist/models/evaluations/evals.d.ts +3 -0
- package/dist/models/evaluations/evals.js +38 -0
- package/dist/models/evaluations/evaluateScenario.d.ts +2 -0
- package/dist/models/evaluations/evaluateScenario.js +23 -0
- package/dist/models/evaluations/evaluateStep.d.ts +2 -0
- package/dist/models/evaluations/evaluateStep.js +55 -0
- package/dist/models/evaluations/evaluateTextResponse.d.ts +8 -0
- package/dist/models/evaluations/evaluateTextResponse.js +28 -0
- package/dist/models/evaluations/evaluateToolCalls.d.ts +6 -0
- package/dist/models/evaluations/evaluateToolCalls.js +61 -0
- package/dist/models/evaluations/index.d.ts +2 -0
- package/dist/models/evaluations/index.js +36 -0
- package/dist/models/evaluations/printEval.d.ts +2 -0
- package/dist/models/evaluations/printEval.js +79 -0
- package/dist/models/evaluations/textSimilaryPrompt.d.ts +1 -0
- package/dist/models/evaluations/textSimilaryPrompt.js +41 -0
- package/dist/models/evaluations/types.d.ts +41 -0
- package/dist/models/evaluations/types.js +8 -0
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +1 -0
- package/dist/models/toolInvocation.d.ts +2 -4
- package/dist/models/toolInvocation.js +7 -5
- package/dist/models/types.d.ts +2 -0
- package/package.json +14 -12
package/dist/Autopilot.js
CHANGED
|
@@ -58,7 +58,7 @@ exports.handleVoiceRequest = handleVoiceRequest;
|
|
|
58
58
|
*/
|
|
59
59
|
const common_1 = require("@fonoster/common");
|
|
60
60
|
const logger_1 = require("@fonoster/logger");
|
|
61
|
-
const createLanguageModel_1 = require("./createLanguageModel");
|
|
61
|
+
const createLanguageModel_1 = require("./models/createLanguageModel");
|
|
62
62
|
const envs_1 = require("./envs");
|
|
63
63
|
const loadAssistantConfigFromFile_1 = require("./loadAssistantConfigFromFile");
|
|
64
64
|
const _1 = __importStar(require("."));
|
|
@@ -66,7 +66,7 @@ const loadAssistantFromAPI_1 = require("./loadAssistantFromAPI");
|
|
|
66
66
|
const fs_1 = __importDefault(require("fs"));
|
|
67
67
|
const logger = (0, logger_1.getLogger)({ service: "autopilot", filePath: __filename });
|
|
68
68
|
async function handleVoiceRequest(req, res) {
|
|
69
|
-
const { accessKeyId, ingressNumber, sessionRef, appRef, callDirection } = req;
|
|
69
|
+
const { accessKeyId, callerNumber, ingressNumber, sessionRef, appRef, callDirection } = req;
|
|
70
70
|
logger.verbose("voice request", {
|
|
71
71
|
accessKeyId,
|
|
72
72
|
ingressNumber,
|
|
@@ -105,8 +105,8 @@ async function handleVoiceRequest(req, res) {
|
|
|
105
105
|
knowledgeBase,
|
|
106
106
|
telephonyContext: {
|
|
107
107
|
callDirection,
|
|
108
|
-
ingressNumber
|
|
109
|
-
callerNumber
|
|
108
|
+
ingressNumber,
|
|
109
|
+
callerNumber
|
|
110
110
|
}
|
|
111
111
|
});
|
|
112
112
|
const { conversationSettings } = assistantConfig;
|
|
@@ -42,44 +42,63 @@ class AbstractLanguageModel {
|
|
|
42
42
|
async invoke(text) {
|
|
43
43
|
const { chain, chatHistory, toolsCatalog } = this;
|
|
44
44
|
const response = (await chain.invoke({ text }));
|
|
45
|
-
let
|
|
45
|
+
let isFirstTool = true;
|
|
46
|
+
logger.verbose("invoke", { text });
|
|
47
|
+
logger.verbose("response", { content: response.content });
|
|
48
|
+
logger.verbose("tools?", {
|
|
49
|
+
hasTools: response.tool_calls?.length > 0,
|
|
50
|
+
tools: response.tool_calls?.map((tool) => tool.name)
|
|
51
|
+
});
|
|
46
52
|
if (response.tool_calls && response.tool_calls.length > 0) {
|
|
47
53
|
// eslint-disable-next-line no-loops/no-loops
|
|
48
54
|
for (const toolCall of response.tool_calls) {
|
|
49
|
-
const { args, name } = toolCall;
|
|
50
|
-
logger.verbose(`invoking tool: ${
|
|
51
|
-
|
|
55
|
+
const { args, name: toolName } = toolCall;
|
|
56
|
+
logger.verbose(`invoking tool: ${toolName} with args: ${JSON.stringify(args)}`, {
|
|
57
|
+
isFirstTool
|
|
52
58
|
});
|
|
53
|
-
switch (
|
|
59
|
+
switch (toolName) {
|
|
54
60
|
case "hangup":
|
|
55
61
|
await chatHistory.addAIMessage("tool result: call hangup initiated");
|
|
56
|
-
return {
|
|
62
|
+
return {
|
|
63
|
+
type: "hangup",
|
|
64
|
+
content: "tool result: call hangup initiated",
|
|
65
|
+
toolCalls: response.tool_calls
|
|
66
|
+
};
|
|
57
67
|
case "transfer":
|
|
58
68
|
await chatHistory.addAIMessage("tool result: call transfer initiated");
|
|
59
|
-
return {
|
|
69
|
+
return {
|
|
70
|
+
type: "transfer",
|
|
71
|
+
content: "tool result: call transfer initiated",
|
|
72
|
+
toolCalls: response.tool_calls
|
|
73
|
+
};
|
|
60
74
|
default:
|
|
75
|
+
if (isFirstTool) {
|
|
76
|
+
const tool = toolsCatalog.getTool(toolName);
|
|
77
|
+
await this.voice.say(tool?.requestStartMessage ?? "");
|
|
78
|
+
}
|
|
61
79
|
await (0, toolInvocation_1.toolInvocation)({
|
|
62
80
|
args,
|
|
63
81
|
chatHistory,
|
|
64
|
-
|
|
65
|
-
toolName
|
|
66
|
-
toolsCatalog
|
|
67
|
-
voice: this.voice
|
|
82
|
+
isFirstTool,
|
|
83
|
+
toolName,
|
|
84
|
+
toolsCatalog
|
|
68
85
|
});
|
|
69
|
-
|
|
86
|
+
isFirstTool = false;
|
|
70
87
|
}
|
|
71
88
|
}
|
|
72
89
|
const finalResponse = (await chain.invoke({
|
|
73
|
-
text: "
|
|
90
|
+
text: "Write a quick message based on the tools results"
|
|
74
91
|
}));
|
|
75
|
-
|
|
92
|
+
logger.verbose("finalResponse by AI", { content: finalResponse.content });
|
|
93
|
+
response.content = finalResponse.content?.toString() ?? "";
|
|
76
94
|
}
|
|
77
95
|
await chatHistory.addUserMessage(text);
|
|
78
96
|
await chatHistory.addAIMessage(response.content?.toString() ?? "");
|
|
79
97
|
logger.verbose("system will say", { content: response.content });
|
|
80
98
|
return {
|
|
81
99
|
type: "say",
|
|
82
|
-
content: response.content.toString()
|
|
100
|
+
content: response.content.toString(),
|
|
101
|
+
toolCalls: response.tool_calls
|
|
83
102
|
};
|
|
84
103
|
}
|
|
85
104
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { AssistantConfig, KnowledgeBase, TelephonyContext, Voice } from "
|
|
1
|
+
import { AssistantConfig, KnowledgeBase, TelephonyContext, Voice } from "..";
|
|
2
2
|
declare function createLanguageModel(params: {
|
|
3
3
|
voice: Voice;
|
|
4
4
|
assistantConfig: AssistantConfig;
|
|
5
5
|
knowledgeBase: KnowledgeBase;
|
|
6
6
|
telephonyContext: TelephonyContext;
|
|
7
|
-
}): import("./
|
|
7
|
+
}): import("./AbstractLanguageModel").AbstractLanguageModel;
|
|
8
8
|
export { createLanguageModel };
|
|
@@ -20,22 +20,17 @@ exports.createLanguageModel = createLanguageModel;
|
|
|
20
20
|
* See the License for the specific language governing permissions and
|
|
21
21
|
* limitations under the License.
|
|
22
22
|
*/
|
|
23
|
-
const
|
|
23
|
+
const __1 = require("..");
|
|
24
24
|
function createLanguageModel(params) {
|
|
25
25
|
const { voice, assistantConfig, knowledgeBase, telephonyContext } = params;
|
|
26
26
|
const { languageModel: languageModelSettings, conversationSettings } = assistantConfig;
|
|
27
|
-
//
|
|
27
|
+
// The transfer tool is only added if the transfer options exist
|
|
28
28
|
const tools = languageModelSettings.tools.concat(assistantConfig.conversationSettings.transferOptions
|
|
29
|
-
? [
|
|
30
|
-
: [
|
|
31
|
-
return
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
maxTokens: languageModelSettings.maxTokens,
|
|
35
|
-
temperature: languageModelSettings.temperature,
|
|
36
|
-
firstMessage: conversationSettings.firstMessage,
|
|
37
|
-
systemPrompt: conversationSettings.systemPrompt,
|
|
38
|
-
baseUrl: languageModelSettings.baseUrl,
|
|
29
|
+
? [__1.hangupToolDefinition, __1.transferToolDefinition]
|
|
30
|
+
: [__1.hangupToolDefinition]);
|
|
31
|
+
return __1.LanguageModelFactory.getLanguageModel(languageModelSettings.provider, {
|
|
32
|
+
...languageModelSettings,
|
|
33
|
+
...conversationSettings,
|
|
39
34
|
knowledgeBase,
|
|
40
35
|
tools
|
|
41
36
|
}, voice, telephonyContext);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createTestTextSimilarity = createTestTextSimilarity;
|
|
4
|
+
/*
|
|
5
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
6
|
+
* http://github.com/fonoster/fonoster
|
|
7
|
+
*
|
|
8
|
+
* This file is part of Fonoster
|
|
9
|
+
*
|
|
10
|
+
* Licensed under the MIT License (the "License");
|
|
11
|
+
* you may not use this file except in compliance with
|
|
12
|
+
* the License. You may obtain a copy of the License at
|
|
13
|
+
*
|
|
14
|
+
* https://opensource.org/licenses/MIT
|
|
15
|
+
*
|
|
16
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
17
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
18
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
19
|
+
* See the License for the specific language governing permissions and
|
|
20
|
+
* limitations under the License.
|
|
21
|
+
*/
|
|
22
|
+
const messages_1 = require("@langchain/core/messages");
|
|
23
|
+
const openai_1 = require("@langchain/openai");
|
|
24
|
+
function createTestTextSimilarity(evalsLanguageModel, systemPrompt) {
|
|
25
|
+
if (!evalsLanguageModel.apiKey) {
|
|
26
|
+
throw new Error("API key is required for text similarity evaluation.");
|
|
27
|
+
}
|
|
28
|
+
return async function testTextSimilarity(text1, text2) {
|
|
29
|
+
const llm = new openai_1.ChatOpenAI({
|
|
30
|
+
modelName: evalsLanguageModel.model,
|
|
31
|
+
temperature: 0,
|
|
32
|
+
openAIApiKey: evalsLanguageModel.apiKey,
|
|
33
|
+
maxTokens: 10
|
|
34
|
+
});
|
|
35
|
+
const messages = [
|
|
36
|
+
new messages_1.SystemMessage(systemPrompt),
|
|
37
|
+
new messages_1.HumanMessage(`Text 1: ${text1}\nText 2: ${text2}`)
|
|
38
|
+
];
|
|
39
|
+
const response = await llm.invoke(messages);
|
|
40
|
+
const reply = response.content?.toString().trim().toLowerCase();
|
|
41
|
+
return new Boolean(reply).valueOf();
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evalTestCases = evalTestCases;
|
|
4
|
+
const createLanguageModel_1 = require("../createLanguageModel");
|
|
5
|
+
const createTestTextSimilarity_1 = require("./createTestTextSimilarity");
|
|
6
|
+
const evaluateScenario_1 = require("./evaluateScenario");
|
|
7
|
+
const textSimilaryPrompt_1 = require("./textSimilaryPrompt");
|
|
8
|
+
async function evalTestCases(assistantConfig) {
|
|
9
|
+
const { testCases } = assistantConfig;
|
|
10
|
+
const voice = {
|
|
11
|
+
say: async (_) => { }
|
|
12
|
+
};
|
|
13
|
+
const evaluationReports = [];
|
|
14
|
+
for (const scenario of testCases?.scenarios) {
|
|
15
|
+
const languageModel = (0, createLanguageModel_1.createLanguageModel)({
|
|
16
|
+
voice,
|
|
17
|
+
assistantConfig,
|
|
18
|
+
knowledgeBase: {
|
|
19
|
+
load: async () => { },
|
|
20
|
+
queryKnowledgeBase: async (query, k) => query
|
|
21
|
+
},
|
|
22
|
+
telephonyContext: scenario.telephonyContext
|
|
23
|
+
});
|
|
24
|
+
const testTextSimilarity = (0, createTestTextSimilarity_1.createTestTextSimilarity)({
|
|
25
|
+
provider: assistantConfig.testCases?.evalsLanguageModel?.provider,
|
|
26
|
+
model: assistantConfig.testCases?.evalsLanguageModel?.model,
|
|
27
|
+
apiKey: assistantConfig.testCases?.evalsLanguageModel?.apiKey
|
|
28
|
+
}, assistantConfig.testCases?.evalsSystemPrompt || textSimilaryPrompt_1.textSimilaryPrompt);
|
|
29
|
+
const evaluationReport = await (0, evaluateScenario_1.evaluateScenario)({
|
|
30
|
+
assistantConfig,
|
|
31
|
+
scenario,
|
|
32
|
+
languageModel,
|
|
33
|
+
testTextSimilarity
|
|
34
|
+
});
|
|
35
|
+
evaluationReports.push(evaluationReport);
|
|
36
|
+
}
|
|
37
|
+
return evaluationReports;
|
|
38
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evaluateScenario = evaluateScenario;
|
|
4
|
+
const evaluateStep_1 = require("./evaluateStep");
|
|
5
|
+
async function evaluateScenario(config) {
|
|
6
|
+
const { scenario, languageModel, testTextSimilarity, assistantConfig } = config;
|
|
7
|
+
const results = [];
|
|
8
|
+
for (const step of scenario.conversation) {
|
|
9
|
+
const stepResult = await (0, evaluateStep_1.evaluateStep)({
|
|
10
|
+
step,
|
|
11
|
+
languageModel,
|
|
12
|
+
testTextSimilarity,
|
|
13
|
+
assistantConfig
|
|
14
|
+
});
|
|
15
|
+
results.push(stepResult);
|
|
16
|
+
}
|
|
17
|
+
const overallPassed = results.every((step) => step.passed);
|
|
18
|
+
return {
|
|
19
|
+
scenarioRef: scenario.ref,
|
|
20
|
+
overallPassed,
|
|
21
|
+
steps: results
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evaluateStep = evaluateStep;
|
|
4
|
+
const evaluateTextResponse_1 = require("./evaluateTextResponse");
|
|
5
|
+
const evaluateToolCalls_1 = require("./evaluateToolCalls");
|
|
6
|
+
async function evaluateStep({ step, languageModel, testTextSimilarity, assistantConfig }) {
|
|
7
|
+
const stepResult = {
|
|
8
|
+
humanInput: step.userInput,
|
|
9
|
+
expectedResponse: step.expected.text.response,
|
|
10
|
+
aiResponse: "", // will be filled if invoke is successful
|
|
11
|
+
evaluationType: step.expected.text.type,
|
|
12
|
+
passed: true
|
|
13
|
+
};
|
|
14
|
+
try {
|
|
15
|
+
const response = await languageModel.invoke(step.userInput);
|
|
16
|
+
// Hangup and transfer are special cases
|
|
17
|
+
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
18
|
+
const topTool = response.toolCalls[0];
|
|
19
|
+
if (topTool.name === "hangup") {
|
|
20
|
+
stepResult.aiResponse =
|
|
21
|
+
assistantConfig.conversationSettings?.goodbyeMessage;
|
|
22
|
+
}
|
|
23
|
+
else if (topTool.name === "transfer") {
|
|
24
|
+
stepResult.aiResponse =
|
|
25
|
+
assistantConfig.conversationSettings?.transferOptions?.message ?? "";
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
stepResult.aiResponse = response.content;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
stepResult.aiResponse = response.content;
|
|
33
|
+
}
|
|
34
|
+
const textEvaluation = await (0, evaluateTextResponse_1.evaluateTextResponse)(step.expected.text, stepResult.aiResponse, testTextSimilarity);
|
|
35
|
+
if (!textEvaluation.passed) {
|
|
36
|
+
stepResult.passed = false;
|
|
37
|
+
stepResult.errorMessage = textEvaluation.errorMessage;
|
|
38
|
+
}
|
|
39
|
+
if (step.expected.tools && step.expected.tools.length > 0) {
|
|
40
|
+
const toolsEvaluation = (0, evaluateToolCalls_1.evaluateToolCalls)(step.expected.tools, response.toolCalls);
|
|
41
|
+
stepResult.toolEvaluations = toolsEvaluation.evaluations;
|
|
42
|
+
if (!toolsEvaluation.passed) {
|
|
43
|
+
stepResult.passed = false;
|
|
44
|
+
stepResult.errorMessage = stepResult.errorMessage
|
|
45
|
+
? `${stepResult.errorMessage} ${toolsEvaluation.errorMessage}`
|
|
46
|
+
: toolsEvaluation.errorMessage;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
stepResult.passed = false;
|
|
52
|
+
stepResult.errorMessage = `Language model error for input "${step.userInput}": ${error}`;
|
|
53
|
+
}
|
|
54
|
+
return stepResult;
|
|
55
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ExpectedTextType } from "./types";
|
|
2
|
+
export declare function evaluateTextResponse(expected: {
|
|
3
|
+
type: ExpectedTextType;
|
|
4
|
+
response: string;
|
|
5
|
+
}, aiResponse: string, testTextSimilarity: (text1: string, text2: string) => Promise<boolean>): Promise<{
|
|
6
|
+
passed: boolean;
|
|
7
|
+
errorMessage?: string;
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.evaluateTextResponse = evaluateTextResponse;
|
|
8
|
+
const types_1 = require("./types");
|
|
9
|
+
async function evaluateTextResponse(expected, aiResponse, testTextSimilarity) {
|
|
10
|
+
if (expected.type === types_1.ExpectedTextType.EXACT) {
|
|
11
|
+
if (aiResponse !== expected.response) {
|
|
12
|
+
return {
|
|
13
|
+
passed: false,
|
|
14
|
+
errorMessage: `Expected exact response "${expected.response}", but got "${aiResponse}".`
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
else if (expected.type === types_1.ExpectedTextType.SIMILAR) {
|
|
19
|
+
const isSimilar = await testTextSimilarity(expected.response, aiResponse);
|
|
20
|
+
if (!isSimilar) {
|
|
21
|
+
return {
|
|
22
|
+
passed: false,
|
|
23
|
+
errorMessage: `Expected similar response to "${expected.response}", but got "${aiResponse}".`
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return { passed: true };
|
|
28
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evaluateToolCalls = evaluateToolCalls;
|
|
4
|
+
function evaluateToolCalls(expectedTools, toolCalls) {
|
|
5
|
+
const evaluations = [];
|
|
6
|
+
let overallPassed = true;
|
|
7
|
+
if (!toolCalls || toolCalls.length !== expectedTools.length) {
|
|
8
|
+
overallPassed = false;
|
|
9
|
+
evaluations.push({
|
|
10
|
+
expectedTool: "",
|
|
11
|
+
actualTool: "",
|
|
12
|
+
passed: false,
|
|
13
|
+
expectedParameters: undefined,
|
|
14
|
+
actualParameters: undefined,
|
|
15
|
+
errorMessage: `Expected ${expectedTools.length} tool invocation(s), but got ${toolCalls ? toolCalls.length : 0}.`
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
evaluations,
|
|
19
|
+
passed: overallPassed,
|
|
20
|
+
errorMessage: `Tool invocation count mismatch.`
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
for (let i = 0; i < expectedTools.length; i++) {
|
|
24
|
+
const expectedTool = expectedTools[i];
|
|
25
|
+
const actualCall = toolCalls[i];
|
|
26
|
+
let toolPassed = true;
|
|
27
|
+
let errorMessage = "";
|
|
28
|
+
if (actualCall.name !== expectedTool.tool) {
|
|
29
|
+
toolPassed = false;
|
|
30
|
+
errorMessage = `Expected tool "${expectedTool.tool}" but got "${actualCall.name}".`;
|
|
31
|
+
}
|
|
32
|
+
// Validate expected parameters against the actual ones
|
|
33
|
+
const expectedParams = expectedTool.parameters || {};
|
|
34
|
+
const actualParams = actualCall.args || {};
|
|
35
|
+
for (const key of Object.keys(expectedParams)) {
|
|
36
|
+
if (actualParams[key] !== expectedParams[key]) {
|
|
37
|
+
toolPassed = false;
|
|
38
|
+
const paramMsg = `Expected parameter "${key}" to have value ${JSON.stringify(expectedParams[key])}, but got ${JSON.stringify(actualParams[key])}.`;
|
|
39
|
+
errorMessage = errorMessage ? errorMessage + " " + paramMsg : paramMsg;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (!toolPassed) {
|
|
43
|
+
overallPassed = false;
|
|
44
|
+
}
|
|
45
|
+
evaluations.push({
|
|
46
|
+
expectedTool: expectedTool.tool,
|
|
47
|
+
actualTool: actualCall.name,
|
|
48
|
+
passed: toolPassed,
|
|
49
|
+
expectedParameters: expectedTool.parameters,
|
|
50
|
+
actualParameters: actualCall.args,
|
|
51
|
+
errorMessage: errorMessage || undefined
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
evaluations,
|
|
56
|
+
passed: overallPassed,
|
|
57
|
+
errorMessage: overallPassed
|
|
58
|
+
? undefined
|
|
59
|
+
: "One or more tool evaluations failed."
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
/*
|
|
18
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
19
|
+
* http://github.com/fonoster/fonoster
|
|
20
|
+
*
|
|
21
|
+
* This file is part of Fonoster
|
|
22
|
+
*
|
|
23
|
+
* Licensed under the MIT License (the "License");
|
|
24
|
+
* you may not use this file except in compliance with
|
|
25
|
+
* the License. You may obtain a copy of the License at
|
|
26
|
+
*
|
|
27
|
+
* https://opensource.org/licenses/MIT
|
|
28
|
+
*
|
|
29
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
30
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
31
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
32
|
+
* See the License for the specific language governing permissions and
|
|
33
|
+
* limitations under the License.
|
|
34
|
+
*/
|
|
35
|
+
__exportStar(require("./evals"), exports);
|
|
36
|
+
__exportStar(require("./printEval"), exports);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.printEval = printEval;
|
|
7
|
+
/*
|
|
8
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
9
|
+
* http://github.com/fonoster/fonoster
|
|
10
|
+
*
|
|
11
|
+
* This file is part of Fonoster
|
|
12
|
+
*
|
|
13
|
+
* Licensed under the MIT License (the "License");
|
|
14
|
+
* you may not use this file except in compliance with
|
|
15
|
+
* the License. You may obtain a copy of the License at
|
|
16
|
+
*
|
|
17
|
+
* https://opensource.org/licenses/MIT
|
|
18
|
+
*
|
|
19
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
20
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
21
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
22
|
+
* See the License for the specific language governing permissions and
|
|
23
|
+
* limitations under the License.
|
|
24
|
+
*/
|
|
25
|
+
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
26
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
27
|
+
function printEval(results) {
|
|
28
|
+
results.forEach((result) => {
|
|
29
|
+
console.log(chalk_1.default.bold.blue(`\nScenario: ${result.scenarioRef}`));
|
|
30
|
+
console.log(chalk_1.default.bold(`Overall Passed: ${result.overallPassed ? chalk_1.default.green("✔") : chalk_1.default.red("✘")}`));
|
|
31
|
+
const table = new cli_table3_1.default({
|
|
32
|
+
head: [
|
|
33
|
+
"Step",
|
|
34
|
+
"Human Input",
|
|
35
|
+
"Expected",
|
|
36
|
+
"AI Response",
|
|
37
|
+
"Tool Calls",
|
|
38
|
+
"Passed"
|
|
39
|
+
],
|
|
40
|
+
colWidths: [8, 25, 25, 25, 25, 8],
|
|
41
|
+
wordWrap: true
|
|
42
|
+
});
|
|
43
|
+
result.steps.forEach((step, index) => {
|
|
44
|
+
// Format tool evaluations if they exist
|
|
45
|
+
let toolEvalText = "";
|
|
46
|
+
if (step.toolEvaluations && step.toolEvaluations.length > 0) {
|
|
47
|
+
toolEvalText = step.toolEvaluations
|
|
48
|
+
.map((toolEval) => {
|
|
49
|
+
const params = JSON.stringify(toolEval.actualParameters || {});
|
|
50
|
+
return `${toolEval.actualTool}(${params})`;
|
|
51
|
+
})
|
|
52
|
+
.join("\n");
|
|
53
|
+
}
|
|
54
|
+
table.push([
|
|
55
|
+
index + 1,
|
|
56
|
+
step.humanInput,
|
|
57
|
+
step.expectedResponse,
|
|
58
|
+
step.aiResponse,
|
|
59
|
+
toolEvalText,
|
|
60
|
+
step.passed ? chalk_1.default.green("✔") : chalk_1.default.red("✘")
|
|
61
|
+
]);
|
|
62
|
+
// Print error message if step failed
|
|
63
|
+
if (!step.passed && step.errorMessage) {
|
|
64
|
+
console.log(chalk_1.default.red(`\nError in step ${index + 1}:`));
|
|
65
|
+
console.log(chalk_1.default.red(step.errorMessage));
|
|
66
|
+
}
|
|
67
|
+
// Print tool evaluation errors if any
|
|
68
|
+
if (step.toolEvaluations) {
|
|
69
|
+
step.toolEvaluations.forEach((toolEval) => {
|
|
70
|
+
if (!toolEval.passed && toolEval.errorMessage) {
|
|
71
|
+
console.log(chalk_1.default.red(`\nTool Error in step ${index + 1}:`));
|
|
72
|
+
console.log(chalk_1.default.red(toolEval.errorMessage));
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
console.log(table.toString());
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const textSimilaryPrompt = "\nYou are a text similarity evaluator for a Voice Assistant application. \n\nGive Text1 and Text2, you use the following process to evaluate the similarity between the two texts:\n\n- Take the first text and determmine the intent of the text.\n- Take the second text and determine the intent of the text.\n- Compare the intents of the two texts ignoring the actual text content and the entities, and length of the text.\n\n## Example 1\n\nText1: \"You're welcome. Have a great day!\"\nText2: \"You're welcome [name]. Your appointment is confirmed. Goodbye!\"\n\nAnswer: true\n\n=== \n\nAre the intents of the two texts the same? Respond with true.\n";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.textSimilaryPrompt = void 0;
|
|
4
|
+
/*
|
|
5
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
6
|
+
* http://github.com/fonoster/fonoster
|
|
7
|
+
*
|
|
8
|
+
* This file is part of Fonoster
|
|
9
|
+
*
|
|
10
|
+
* Licensed under the MIT License (the "License");
|
|
11
|
+
* you may not use this file except in compliance with
|
|
12
|
+
* the License. You may obtain a copy of the License at
|
|
13
|
+
*
|
|
14
|
+
* https://opensource.org/licenses/MIT
|
|
15
|
+
*
|
|
16
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
17
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
18
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
19
|
+
* See the License for the specific language governing permissions and
|
|
20
|
+
* limitations under the License.
|
|
21
|
+
*/
|
|
22
|
+
exports.textSimilaryPrompt = `
|
|
23
|
+
You are a text similarity evaluator for a Voice Assistant application.
|
|
24
|
+
|
|
25
|
+
Give Text1 and Text2, you use the following process to evaluate the similarity between the two texts:
|
|
26
|
+
|
|
27
|
+
- Take the first text and determmine the intent of the text.
|
|
28
|
+
- Take the second text and determine the intent of the text.
|
|
29
|
+
- Compare the intents of the two texts ignoring the actual text content and the entities, and length of the text.
|
|
30
|
+
|
|
31
|
+
## Example 1
|
|
32
|
+
|
|
33
|
+
Text1: "You're welcome. Have a great day!"
|
|
34
|
+
Text2: "You're welcome [name]. Your appointment is confirmed. Goodbye!"
|
|
35
|
+
|
|
36
|
+
Answer: true
|
|
37
|
+
|
|
38
|
+
===
|
|
39
|
+
|
|
40
|
+
Are the intents of the two texts the same? Respond with true.
|
|
41
|
+
`;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { AssistantConfig } from "../../assistants";
|
|
2
|
+
import { LanguageModel } from "../types";
|
|
3
|
+
declare enum ExpectedTextType {
|
|
4
|
+
EXACT = "exact",
|
|
5
|
+
SIMILAR = "similar"
|
|
6
|
+
}
|
|
7
|
+
type ScenarioEvaluationReport = {
|
|
8
|
+
scenarioRef: string;
|
|
9
|
+
overallPassed: boolean;
|
|
10
|
+
steps: StepEvaluationReport[];
|
|
11
|
+
};
|
|
12
|
+
type StepEvaluationReport = {
|
|
13
|
+
humanInput: string;
|
|
14
|
+
expectedResponse: string;
|
|
15
|
+
aiResponse: string;
|
|
16
|
+
evaluationType: ExpectedTextType;
|
|
17
|
+
passed: boolean;
|
|
18
|
+
errorMessage?: string;
|
|
19
|
+
toolEvaluations?: ToolEvaluationReport[];
|
|
20
|
+
};
|
|
21
|
+
type EvaluateStepParams = {
|
|
22
|
+
step: any;
|
|
23
|
+
languageModel: LanguageModel;
|
|
24
|
+
testTextSimilarity: (text1: string, text2: string) => Promise<boolean>;
|
|
25
|
+
assistantConfig: AssistantConfig;
|
|
26
|
+
};
|
|
27
|
+
type ToolEvaluationReport = {
|
|
28
|
+
expectedTool: string;
|
|
29
|
+
actualTool: string;
|
|
30
|
+
passed: boolean;
|
|
31
|
+
expectedParameters?: Record<string, unknown>;
|
|
32
|
+
actualParameters?: Record<string, unknown>;
|
|
33
|
+
errorMessage?: string;
|
|
34
|
+
};
|
|
35
|
+
type ScenarioEvaluationConfig = {
|
|
36
|
+
assistantConfig: AssistantConfig;
|
|
37
|
+
scenario: any;
|
|
38
|
+
languageModel: LanguageModel;
|
|
39
|
+
testTextSimilarity: (text1: string, text2: string) => Promise<boolean>;
|
|
40
|
+
};
|
|
41
|
+
export { ExpectedTextType, ScenarioEvaluationReport, StepEvaluationReport, ToolEvaluationReport, ScenarioEvaluationConfig, EvaluateStepParams };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExpectedTextType = void 0;
|
|
4
|
+
var ExpectedTextType;
|
|
5
|
+
(function (ExpectedTextType) {
|
|
6
|
+
ExpectedTextType["EXACT"] = "exact";
|
|
7
|
+
ExpectedTextType["SIMILAR"] = "similar";
|
|
8
|
+
})(ExpectedTextType || (exports.ExpectedTextType = ExpectedTextType = {}));
|
package/dist/models/index.d.ts
CHANGED
package/dist/models/index.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { createChatHistory } from "./chatHistory";
|
|
2
2
|
import { ToolsCatalog } from "../tools";
|
|
3
|
-
import { Voice } from "../voice";
|
|
4
3
|
declare function toolInvocation(params: {
|
|
5
4
|
toolName: string;
|
|
6
5
|
chatHistory: ReturnType<typeof createChatHistory>;
|
|
7
6
|
toolsCatalog: ToolsCatalog;
|
|
8
|
-
|
|
7
|
+
isFirstTool: boolean;
|
|
9
8
|
args: Record<string, unknown>;
|
|
10
|
-
|
|
11
|
-
}): Promise<void>;
|
|
9
|
+
}): Promise<string>;
|
|
12
10
|
export { toolInvocation };
|
|
@@ -22,21 +22,23 @@ exports.toolInvocation = toolInvocation;
|
|
|
22
22
|
const logger_1 = require("@fonoster/logger");
|
|
23
23
|
const logger = (0, logger_1.getLogger)({ service: "autopilot", filePath: __filename });
|
|
24
24
|
async function toolInvocation(params) {
|
|
25
|
-
const {
|
|
25
|
+
const { isFirstTool, args, toolName, chatHistory, toolsCatalog } = params;
|
|
26
26
|
try {
|
|
27
|
-
if (
|
|
27
|
+
if (isFirstTool) {
|
|
28
28
|
const tool = toolsCatalog.getTool(toolName);
|
|
29
29
|
const message = tool?.requestStartMessage ?? "";
|
|
30
30
|
if (message) {
|
|
31
|
-
await
|
|
31
|
+
await chatHistory.addAIMessage(message);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
const toolResult = await toolsCatalog.invokeTool(toolName, args);
|
|
35
|
-
logger.verbose(
|
|
36
|
-
await chatHistory.addAIMessage(`tool result: ${toolResult.result}`);
|
|
35
|
+
logger.verbose(`tool result (${toolName}):`, { result: toolResult.result });
|
|
36
|
+
await chatHistory.addAIMessage(`tool result (${toolName}): ${toolResult.result}`);
|
|
37
|
+
return toolResult.result;
|
|
37
38
|
}
|
|
38
39
|
catch (error) {
|
|
39
40
|
logger.error(`tool error: ${error.message}`);
|
|
40
41
|
await chatHistory.addAIMessage(`tool error: ${error.message}`);
|
|
42
|
+
return "";
|
|
41
43
|
}
|
|
42
44
|
}
|
package/dist/models/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { CallDirection } from "@fonoster/types";
|
|
|
2
2
|
import { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
|
3
3
|
import { KnowledgeBase } from "../knowledge";
|
|
4
4
|
import { Tool } from "../tools/type";
|
|
5
|
+
import { ToolCall } from "@langchain/core/messages/tool";
|
|
5
6
|
type LanguageModel = {
|
|
6
7
|
invoke: (text: string) => Promise<InvocationResult>;
|
|
7
8
|
};
|
|
@@ -18,6 +19,7 @@ type LanguageModelParams = BaseModelParams & {
|
|
|
18
19
|
type InvocationResult = {
|
|
19
20
|
type: "say" | "hangup" | "transfer";
|
|
20
21
|
content?: string;
|
|
22
|
+
toolCalls?: ToolCall[];
|
|
21
23
|
};
|
|
22
24
|
type TelephonyContext = {
|
|
23
25
|
callDirection: CallDirection;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fonoster/autopilot",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.59",
|
|
4
4
|
"description": "Voice AI for the Fonoster platform",
|
|
5
5
|
"author": "Pedro Sanders <psanders@fonoster.com>",
|
|
6
6
|
"homepage": "https://github.com/fonoster/fonoster#readme",
|
|
@@ -33,18 +33,20 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@aws-sdk/client-s3": "^3.712.0",
|
|
36
|
-
"@fonoster/common": "^0.8.
|
|
37
|
-
"@fonoster/logger": "^0.8.
|
|
38
|
-
"@fonoster/sdk": "^0.8.
|
|
39
|
-
"@fonoster/types": "^0.8.
|
|
40
|
-
"@fonoster/voice": "^0.8.
|
|
41
|
-
"@langchain/community": "^0.3.
|
|
42
|
-
"@langchain/core": "^0.3.
|
|
43
|
-
"@langchain/groq": "^0.1.
|
|
44
|
-
"@langchain/ollama": "^0.1.
|
|
45
|
-
"@langchain/openai": "^0.3
|
|
36
|
+
"@fonoster/common": "^0.8.59",
|
|
37
|
+
"@fonoster/logger": "^0.8.59",
|
|
38
|
+
"@fonoster/sdk": "^0.8.59",
|
|
39
|
+
"@fonoster/types": "^0.8.59",
|
|
40
|
+
"@fonoster/voice": "^0.8.59",
|
|
41
|
+
"@langchain/community": "^0.3.29",
|
|
42
|
+
"@langchain/core": "^0.3.39",
|
|
43
|
+
"@langchain/groq": "^0.1.3",
|
|
44
|
+
"@langchain/ollama": "^0.1.5",
|
|
45
|
+
"@langchain/openai": "^0.4.3",
|
|
46
46
|
"cheerio": "^1.0.0",
|
|
47
|
+
"cli-table3": "^0.6.5",
|
|
47
48
|
"dotenv": "^16.4.5",
|
|
49
|
+
"js-yaml": "^4.1.0",
|
|
48
50
|
"langchain": "^0.3.6",
|
|
49
51
|
"onnxruntime-node": "^1.19.0",
|
|
50
52
|
"pdf-parse": "^1.1.1",
|
|
@@ -55,5 +57,5 @@
|
|
|
55
57
|
"devDependencies": {
|
|
56
58
|
"typescript": "^5.5.4"
|
|
57
59
|
},
|
|
58
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "6a5255febb9e4bcd9184d93abe72281f945c8c4d"
|
|
59
61
|
}
|