@fonoster/autopilot 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -201,4 +201,31 @@ Please use the following environment variables to configure the Autopilot:
201
201
 
202
202
  The Autopilot is still under heavy development. The next steps are to add support for Retrieval-Augmented Generation(RAG), improve the language model integration, and improve the Finite State Machine (FSM) that powers the conversations. If you have any suggestions or feedback, please let us know.
203
203
 
204
+ ## Audio filters
205
+
206
+ Background voices reach speech recognition and the voice activity detector, and the assistant
207
+ answers them. An assistant can ask the Media Server to filter the caller's audio first, by adding
208
+ `audioFilters` to its configuration — the same block whether the assistant comes from a file or
209
+ from an Application:
210
+
211
+ ```yaml
212
+ audioFilters:
213
+ - name: aiCoustics
214
+ options:
215
+ enhancementLevel: 0.8 # 0 to 1; higher also suppresses competing voices
216
+
217
+ conversationSettings:
218
+ firstMessage: "Hola, ¿en qué puedo ayudarte?"
219
+ # ...
220
+ ```
221
+
222
+ For an Application, the same block goes inside `intelligence.config`, and is applied with:
223
+
224
+ ```sh
225
+ fonoster applications:create -f app.yaml
226
+ ```
204
227
 
228
+ Assistant files may be YAML or JSON. Filtering needs the optional `@ai-coustics/aic-sdk` package
229
+ and an `AIC_SDK_LICENSE` key on the apiserver; the key never appears in assistant configuration,
230
+ so these files stay safe to commit. If the filter cannot be applied, the call continues with
231
+ unfiltered audio and the reason is logged.
@@ -6,6 +6,7 @@ declare class Autopilot {
6
6
  constructor(params: AutopilotParams);
7
7
  start(): Promise<void>;
8
8
  stop(): void;
9
+ private setupAudioFilters;
9
10
  private setupVoiceStream;
10
11
  private handleVoicePayload;
11
12
  private setupSpeechGathering;
package/dist/Autopilot.js CHANGED
@@ -41,6 +41,7 @@ class Autopilot {
41
41
  });
42
42
  }
43
43
  async start() {
44
+ await this.setupAudioFilters();
44
45
  const vadParams = this.params.conversationSettings.vad;
45
46
  const sileroVad = new vad_1.SileroVad({
46
47
  pathToModel: vadParams.pathToModel ||
@@ -63,6 +64,26 @@ class Autopilot {
63
64
  logger.verbose("stopping autopilot");
64
65
  this.actor.stop();
65
66
  }
67
+ // Asked for before the audio stream is opened, so voice activity detection
68
+ // and speech recognition both see filtered audio from the first frame. A
69
+ // refusal is logged and the call continues unfiltered.
70
+ async setupAudioFilters() {
71
+ const { voice, audioFilters } = this.params;
72
+ if (!audioFilters?.length) {
73
+ return;
74
+ }
75
+ try {
76
+ await voice.setAudioFilters(audioFilters);
77
+ logger.verbose("audio filters requested", {
78
+ filters: audioFilters.map((filter) => filter.name)
79
+ });
80
+ }
81
+ catch (err) {
82
+ logger.warn("could not set audio filters; continuing unfiltered", {
83
+ error: err?.message
84
+ });
85
+ }
86
+ }
66
87
  async setupVoiceStream() {
67
88
  const { voice } = this.params;
68
89
  const stream = await voice.stream();
@@ -1,3 +1,3 @@
1
1
  import { AssistantConfig } from "./types";
2
- declare function loadAndValidateAssistant(path: string): AssistantConfig;
2
+ declare function loadAndValidateAssistant(assistantPath: string): AssistantConfig;
3
3
  export { loadAndValidateAssistant };
@@ -23,22 +23,25 @@ exports.loadAndValidateAssistant = loadAndValidateAssistant;
23
23
  * limitations under the License.
24
24
  */
25
25
  const fs_1 = __importDefault(require("fs"));
26
+ const path_1 = __importDefault(require("path"));
26
27
  const common_1 = require("@fonoster/common");
27
28
  const logger_1 = require("@fonoster/logger");
29
+ const js_yaml_1 = require("js-yaml");
28
30
  const logger = (0, logger_1.getLogger)({ service: "autopilot", filePath: __filename });
29
- function loadAndValidateAssistant(path) {
30
- if (!fs_1.default.existsSync(path)) {
31
- logger.error("assistant file not found", { path });
31
+ function loadAndValidateAssistant(assistantPath) {
32
+ if (!fs_1.default.existsSync(assistantPath)) {
33
+ logger.error("assistant file not found", { path: assistantPath });
32
34
  process.exit(1);
33
35
  }
34
36
  try {
35
- const fileContent = fs_1.default.readFileSync(path, "utf8");
36
- const assistant = JSON.parse(fileContent);
37
+ const fileContent = fs_1.default.readFileSync(assistantPath, "utf8");
38
+ const isYaml = [".yaml", ".yml"].includes(path_1.default.extname(assistantPath).toLowerCase());
39
+ const assistant = (isYaml ? (0, js_yaml_1.load)(fileContent) : JSON.parse(fileContent));
37
40
  return common_1.assistantSchema.parse(assistant);
38
41
  }
39
42
  catch (e) {
40
43
  logger.error("error parsing or validating assistant file", {
41
- path,
44
+ path: assistantPath,
42
45
  error: e
43
46
  });
44
47
  process.exit(1);
@@ -120,6 +120,7 @@ async function handleVoiceRequest(req, res) {
120
120
  const { conversationSettings } = assistantConfig;
121
121
  try {
122
122
  const autopilot = new _1.default({
123
+ audioFilters: assistantConfig.audioFilters,
123
124
  conversationSettings: conversationSettings,
124
125
  voice: voice,
125
126
  languageModel: languageModel
@@ -86,6 +86,10 @@ export declare function createEvalEffectiveConfig(config: AssistantConfig, crede
86
86
  }[];
87
87
  }[];
88
88
  };
89
+ audioFilters?: {
90
+ options?: Record<string, unknown>;
91
+ name?: string;
92
+ }[];
89
93
  conversationSettings?: {
90
94
  firstMessage?: string;
91
95
  systemPrompt?: string;
package/dist/types.d.ts CHANGED
@@ -16,6 +16,7 @@
16
16
  * See the License for the specific language governing permissions and
17
17
  * limitations under the License.
18
18
  */
19
+ import { AudioFilterConfig } from "@fonoster/common";
19
20
  import { Application } from "@fonoster/types";
20
21
  import { AssistantConfig, ConversationSettings } from "./assistants";
21
22
  import { LanguageModel } from "./models";
@@ -28,6 +29,7 @@ type AutopilotParams = {
28
29
  voice: Voice;
29
30
  conversationSettings: ConversationSettings;
30
31
  languageModel: LanguageModel;
32
+ audioFilters?: AudioFilterConfig[];
31
33
  };
32
34
  type AutopilotApplication = Application & {
33
35
  intelligence: {
@@ -1,3 +1,22 @@
1
+ /**
2
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
3
+ * http://github.com/fonoster/fonoster
4
+ *
5
+ * This file is part of Fonoster
6
+ *
7
+ * Licensed under the MIT License (the "License");
8
+ * you may not use this file except in compliance with
9
+ * the License. You may obtain a copy of the License at
10
+ *
11
+ * https://opensource.org/licenses/MIT
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+ import { AudioFilterConfig } from "@fonoster/common";
1
20
  import { VoiceResponse } from "@fonoster/voice";
2
21
  import { Voice } from "./types";
3
22
  declare class VoiceImpl implements Voice {
@@ -17,6 +36,7 @@ declare class VoiceImpl implements Voice {
17
36
  };
18
37
  constructor(mediaSessionRef: string, voice: VoiceResponse);
19
38
  answer(): Promise<void>;
39
+ setAudioFilters(filters: AudioFilterConfig[]): Promise<void>;
20
40
  hangup(): Promise<void>;
21
41
  say(text: string): Promise<void>;
22
42
  playDtmf(dtmf: string): Promise<void>;
@@ -30,6 +30,9 @@ class VoiceImpl {
30
30
  async answer() {
31
31
  await this.voice.answer();
32
32
  }
33
+ async setAudioFilters(filters) {
34
+ await this.voice.setAudioFilters(filters);
35
+ }
33
36
  async hangup() {
34
37
  await this.voice.hangup();
35
38
  }
@@ -16,6 +16,7 @@
16
16
  * See the License for the specific language governing permissions and
17
17
  * limitations under the License.
18
18
  */
19
+ import { AudioFilterConfig } from "@fonoster/common";
19
20
  type GatherStream = {
20
21
  stop: () => Promise<void>;
21
22
  onData: (cb: (payload: {
@@ -34,6 +35,7 @@ type TransferOptions = {
34
35
  type Voice = {
35
36
  mediaSessionRef: string;
36
37
  answer: () => Promise<void>;
38
+ setAudioFilters: (filters: AudioFilterConfig[]) => Promise<void>;
37
39
  hangup: () => Promise<void>;
38
40
  say: (text: string) => Promise<void>;
39
41
  playDtmf: (dtmf: string) => Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fonoster/autopilot",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
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",
@@ -35,11 +35,11 @@
35
35
  "dependencies": {
36
36
  "@aws-sdk/client-s3": "^3.958.0",
37
37
  "@dmitryrechkin/json-schema-to-zod": "^1.0.1",
38
- "@fonoster/common": "^0.25.0",
39
- "@fonoster/logger": "^0.25.0",
40
- "@fonoster/sdk": "^0.25.0",
41
- "@fonoster/types": "^0.25.0",
42
- "@fonoster/voice": "^0.25.0",
38
+ "@fonoster/common": "^0.26.0",
39
+ "@fonoster/logger": "^0.26.0",
40
+ "@fonoster/sdk": "^0.26.0",
41
+ "@fonoster/types": "^0.26.0",
42
+ "@fonoster/voice": "^0.26.0",
43
43
  "@langchain/anthropic": "^1.3.3",
44
44
  "@langchain/community": "^1.1.1",
45
45
  "@langchain/core": "^1.1.8",
@@ -59,5 +59,5 @@
59
59
  "xstate": "^5.17.3",
60
60
  "zod": "^3.25.76"
61
61
  },
62
- "gitHead": "3f63a173d31c0dbf2391b1eff907c8880e60614d"
62
+ "gitHead": "d14f414d791a33f92e8c4225f7e462abc440a142"
63
63
  }