@fonoster/autopilot 0.8.52 → 0.8.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/Autopilot.js +1 -1
  2. package/dist/handleVoiceRequest.js +4 -3
  3. package/dist/models/AbstractLanguageModel.js +34 -15
  4. package/dist/{createLanguageModel.d.ts → models/createLanguageModel.d.ts} +2 -2
  5. package/dist/{createLanguageModel.js → models/createLanguageModel.js} +7 -12
  6. package/dist/models/evaluations/createTestTextSimilarity.d.ts +6 -0
  7. package/dist/models/evaluations/createTestTextSimilarity.js +43 -0
  8. package/dist/models/evaluations/evals.d.ts +3 -0
  9. package/dist/models/evaluations/evals.js +38 -0
  10. package/dist/models/evaluations/evaluateScenario.d.ts +2 -0
  11. package/dist/models/evaluations/evaluateScenario.js +23 -0
  12. package/dist/models/evaluations/evaluateStep.d.ts +2 -0
  13. package/dist/models/evaluations/evaluateStep.js +55 -0
  14. package/dist/models/evaluations/evaluateTextResponse.d.ts +8 -0
  15. package/dist/models/evaluations/evaluateTextResponse.js +28 -0
  16. package/dist/models/evaluations/evaluateToolCalls.d.ts +6 -0
  17. package/dist/models/evaluations/evaluateToolCalls.js +61 -0
  18. package/dist/models/evaluations/index.d.ts +2 -0
  19. package/dist/models/evaluations/index.js +36 -0
  20. package/dist/models/evaluations/printEval.d.ts +2 -0
  21. package/dist/models/evaluations/printEval.js +79 -0
  22. package/dist/models/evaluations/textSimilaryPrompt.d.ts +1 -0
  23. package/dist/models/evaluations/textSimilaryPrompt.js +41 -0
  24. package/dist/models/evaluations/types.d.ts +41 -0
  25. package/dist/models/evaluations/types.js +8 -0
  26. package/dist/models/index.d.ts +1 -0
  27. package/dist/models/index.js +1 -0
  28. package/dist/models/toolInvocation.d.ts +2 -4
  29. package/dist/models/toolInvocation.js +7 -5
  30. package/dist/models/types.d.ts +2 -0
  31. package/package.json +14 -12
package/dist/Autopilot.js CHANGED
@@ -39,7 +39,7 @@ class Autopilot {
39
39
  this.actor = (0, xstate_1.createActor)(machine_1.machine, {
40
40
  input: {
41
41
  conversationSettings,
42
- languageModel: languageModel,
42
+ languageModel,
43
43
  voice
44
44
  }
45
45
  });
@@ -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("."));
@@ -99,14 +99,15 @@ async function handleVoiceRequest(req, res) {
99
99
  logger.verbose("knowledge base loaded");
100
100
  });
101
101
  const voice = new _1.VoiceImpl(sessionRef, res);
102
+ const { ingressNumber, callerNumber, callDirection } = req;
102
103
  const languageModel = (0, createLanguageModel_1.createLanguageModel)({
103
104
  voice,
104
105
  assistantConfig,
105
106
  knowledgeBase,
106
107
  telephonyContext: {
107
108
  callDirection,
108
- ingressNumber: req.ingressNumber,
109
- callerNumber: req.callerNumber
109
+ ingressNumber,
110
+ callerNumber
110
111
  }
111
112
  });
112
113
  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 firstInvocation = true;
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: ${name} with args: ${JSON.stringify(args)}`, {
51
- firstInvocation
55
+ const { args, name: toolName } = toolCall;
56
+ logger.verbose(`invoking tool: ${toolName} with args: ${JSON.stringify(args)}`, {
57
+ isFirstTool
52
58
  });
53
- switch (name) {
59
+ switch (toolName) {
54
60
  case "hangup":
55
61
  await chatHistory.addAIMessage("tool result: call hangup initiated");
56
- return { type: "hangup" };
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 { type: "transfer" };
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
- firstInvocation,
65
- toolName: name,
66
- toolsCatalog,
67
- voice: this.voice
82
+ isFirstTool,
83
+ toolName,
84
+ toolsCatalog
68
85
  });
69
- firstInvocation = false;
86
+ isFirstTool = false;
70
87
  }
71
88
  }
72
89
  const finalResponse = (await chain.invoke({
73
- text: "Please provide a final response based on the tool's results."
90
+ text: "Write a quick message based on the tools results"
74
91
  }));
75
- response.content = finalResponse.content ?? "";
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("./models/AbstractLanguageModel").AbstractLanguageModel;
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 _1 = require(".");
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
- // Ensure that the transfer tool is only added if the transfer options exist
27
+ // The transfer tool is only added if the transfer options exist
28
28
  const tools = languageModelSettings.tools.concat(assistantConfig.conversationSettings.transferOptions
29
- ? [_1.hangupToolDefinition, _1.transferToolDefinition]
30
- : [_1.hangupToolDefinition]);
31
- return _1.LanguageModelFactory.getLanguageModel(languageModelSettings.provider, {
32
- model: languageModelSettings.model,
33
- apiKey: languageModelSettings.apiKey,
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,6 @@
1
+ export declare function createTestTextSimilarity(evalsLanguageModel: {
2
+ provider: any;
3
+ model: string;
4
+ baseUrl?: string;
5
+ apiKey?: string;
6
+ }, systemPrompt: string): (text1: string, text2: string) => Promise<boolean>;
@@ -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,3 @@
1
+ import { AssistantConfig } from "../../assistants/types";
2
+ import { ScenarioEvaluationReport } from "./types";
3
+ export declare function evalTestCases(assistantConfig: AssistantConfig): Promise<ScenarioEvaluationReport[]>;
@@ -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,2 @@
1
+ import { ScenarioEvaluationConfig, ScenarioEvaluationReport } from "./types";
2
+ export declare function evaluateScenario(config: ScenarioEvaluationConfig): Promise<ScenarioEvaluationReport>;
@@ -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,2 @@
1
+ import { EvaluateStepParams, StepEvaluationReport } from "./types";
2
+ export declare function evaluateStep({ step, languageModel, testTextSimilarity, assistantConfig }: EvaluateStepParams): Promise<StepEvaluationReport>;
@@ -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,6 @@
1
+ import { ToolEvaluationReport } from "./types";
2
+ export declare function evaluateToolCalls(expectedTools: any[], toolCalls: any[] | undefined): {
3
+ evaluations: ToolEvaluationReport[];
4
+ passed: boolean;
5
+ errorMessage?: string;
6
+ };
@@ -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,2 @@
1
+ export * from "./evals";
2
+ export * from "./printEval";
@@ -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,2 @@
1
+ import { ScenarioEvaluationReport } from "./types";
2
+ export declare function printEval(results: ScenarioEvaluationReport[]): void;
@@ -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 the 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 the 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 = {}));
@@ -1,2 +1,3 @@
1
1
  export * from "./LanguageModelFactory";
2
2
  export * from "./types";
3
+ export * from "./evaluations";
@@ -34,3 +34,4 @@ __exportStar(require("./LanguageModelFactory"), exports);
34
34
  * limitations under the License.
35
35
  */
36
36
  __exportStar(require("./types"), exports);
37
+ __exportStar(require("./evaluations"), exports);
@@ -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
- firstInvocation: boolean;
7
+ isFirstTool: boolean;
9
8
  args: Record<string, unknown>;
10
- voice: Voice;
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 { firstInvocation, voice, args, toolName, chatHistory, toolsCatalog } = params;
25
+ const { isFirstTool, args, toolName, chatHistory, toolsCatalog } = params;
26
26
  try {
27
- if (firstInvocation) {
27
+ if (isFirstTool) {
28
28
  const tool = toolsCatalog.getTool(toolName);
29
29
  const message = tool?.requestStartMessage ?? "";
30
30
  if (message) {
31
- await voice.say(message);
31
+ await chatHistory.addAIMessage(message);
32
32
  }
33
33
  }
34
34
  const toolResult = await toolsCatalog.invokeTool(toolName, args);
35
- logger.verbose("tool result: ", toolResult);
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
  }
@@ -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.52",
3
+ "version": "0.8.56",
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.52",
37
- "@fonoster/logger": "^0.8.51",
38
- "@fonoster/sdk": "^0.8.52",
39
- "@fonoster/types": "^0.8.51",
40
- "@fonoster/voice": "^0.8.52",
41
- "@langchain/community": "^0.3.19",
42
- "@langchain/core": "^0.3.23",
43
- "@langchain/groq": "^0.1.2",
44
- "@langchain/ollama": "^0.1.2",
45
- "@langchain/openai": "^0.3.14",
36
+ "@fonoster/common": "^0.8.56",
37
+ "@fonoster/logger": "^0.8.56",
38
+ "@fonoster/sdk": "^0.8.56",
39
+ "@fonoster/types": "^0.8.56",
40
+ "@fonoster/voice": "^0.8.56",
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": "772398ea115078012f8e270943ec36b61343ea36"
60
+ "gitHead": "eb95cb72d964068fe3d525b696f17c4d4b0522a2"
59
61
  }