refactor: remove conditional voice mode support from backend and frontend (#8597)

* revert changes that made voice mode conditional to the presence of webrtcvad

* refactor: lazy-load webrtcvad dependency

* refactor: remove try-except block and directly import voice_mode_router

* [autofix.ci] apply automated fixes

* feat: add LRU caching to voice activity detector initialization

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Gabriel Luiz Freitas Almeida 2025-06-18 09:23:31 -03:00 • committed by GitHub
commit 6ada07a61f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 22 additions and 46 deletions

View file

@ -19,6 +19,7 @@ from langflow.api.v1 import (
validate_router, validate_router,
variables_router, variables_router,
) )
from langflow.api.v1.voice_mode import router as voice_mode_router
from langflow.api.v2 import files_router as files_router_v2 from langflow.api.v2 import files_router as files_router_v2
from langflow.api.v2 import mcp_router as mcp_router_v2 from langflow.api.v2 import mcp_router as mcp_router_v2
@ -45,18 +46,12 @@ router_v1.include_router(folders_router)
router_v1.include_router(projects_router) router_v1.include_router(projects_router)
router_v1.include_router(starter_projects_router) router_v1.include_router(starter_projects_router)
router_v1.include_router(mcp_router) router_v1.include_router(mcp_router)
router_v1.include_router(voice_mode_router)
router_v1.include_router(mcp_projects_router) router_v1.include_router(mcp_projects_router)
router_v2.include_router(files_router_v2) router_v2.include_router(files_router_v2)
router_v2.include_router(mcp_router_v2) router_v2.include_router(mcp_router_v2)
try:
from langflow.api.v1.voice_mode import router as voice_mode_router
router_v1.include_router(voice_mode_router)
except ImportError:
pass
router = APIRouter( router = APIRouter(
prefix="/api", prefix="/api",
) )

View file

@ -378,11 +378,3 @@ async def verify_public_flow_and_get_user(flow_id: uuid.UUID, client_id: str | N
raise HTTPException(status_code=403, detail=msg) raise HTTPException(status_code=403, detail=msg)
return user, new_flow_id return user, new_flow_id
def get_voice_mode_enabled() -> bool:
try:
import webrtcvad # noqa: F401
except ImportError:
return False
return True

View file

@ -14,6 +14,7 @@ from langflow.api.v1.store import router as store_router
from langflow.api.v1.users import router as users_router from langflow.api.v1.users import router as users_router
from langflow.api.v1.validate import router as validate_router from langflow.api.v1.validate import router as validate_router
from langflow.api.v1.variable import router as variables_router from langflow.api.v1.variable import router as variables_router
from langflow.api.v1.voice_mode import router as voice_mode_router
__all__ = [ __all__ = [
"api_key_router", "api_key_router",
@ -32,11 +33,5 @@ __all__ = [
"users_router", "users_router",
"validate_router", "validate_router",
"variables_router", "variables_router",
"voice_mode_router",
] ]
try:
from langflow.api.v1.voice_mode import router as voice_mode_router
__all__ += ["voice_mode_router"]
except ImportError:
pass

View file

@ -14,7 +14,7 @@ from fastapi.responses import StreamingResponse
from loguru import logger from loguru import logger
from sqlmodel import select from sqlmodel import select
from langflow.api.utils import CurrentActiveUser, DbSession, get_voice_mode_enabled, parse_value from langflow.api.utils import CurrentActiveUser, DbSession, parse_value
from langflow.api.v1.schemas import ( from langflow.api.v1.schemas import (
ConfigResponse, ConfigResponse,
CustomComponentRequest, CustomComponentRequest,
@ -752,7 +752,6 @@ async def get_config():
return { return {
"feature_flags": FEATURE_FLAGS, "feature_flags": FEATURE_FLAGS,
**settings_service.settings.model_dump(), **settings_service.settings.model_dump(),
"voice_mode_enabled": get_voice_mode_enabled(),
} }
except Exception as exc: except Exception as exc:
raise HTTPException(status_code=500, detail=str(exc)) from exc raise HTTPException(status_code=500, detail=str(exc)) from exc

View file

@ -388,7 +388,6 @@ class ConfigResponse(BaseModel):
public_flow_cleanup_interval: int public_flow_cleanup_interval: int
public_flow_expiration: int public_flow_expiration: int
event_delivery: Literal["polling", "streaming", "direct"] event_delivery: Literal["polling", "streaming", "direct"]
voice_mode_enabled: bool
class CancelFlowResponse(BaseModel): class CancelFlowResponse(BaseModel):

View file

@ -7,14 +7,13 @@ import traceback
import uuid import uuid
from collections import defaultdict from collections import defaultdict
from datetime import datetime, timezone from datetime import datetime, timezone
from functools import partial from functools import lru_cache, partial
from typing import Any from typing import Any
from uuid import UUID, uuid4 from uuid import UUID, uuid4
import numpy as np import numpy as np
import requests import requests
import sqlalchemy import sqlalchemy
import webrtcvad
import websockets import websockets
from cryptography.fernet import InvalidToken from cryptography.fernet import InvalidToken
from elevenlabs import ElevenLabs from elevenlabs import ElevenLabs
@ -83,6 +82,13 @@ CLIENT_TO_LF = "Client → LF"
# --- Helper Functions --- # --- Helper Functions ---
@lru_cache(maxsize=1)
def get_vad():
import webrtcvad
return webrtcvad.Vad(mode=3)
async def authenticate_and_get_openai_key(session: DbSession, user: User, websocket: WebSocket): async def authenticate_and_get_openai_key(session: DbSession, user: User, websocket: WebSocket):
"""Authenticate the user using a token or API key and retrieve the OpenAI API key. """Authenticate the user using a token or API key and retrieve the OpenAI API key.
@ -129,7 +135,7 @@ class VoiceConfig:
self.elevenlabs_model = "eleven_multilingual_v2" self.elevenlabs_model = "eleven_multilingual_v2"
self.elevenlabs_client = None self.elevenlabs_client = None
self.elevenlabs_key = None self.elevenlabs_key = None
self.barge_in_enabled = False self._barge_in_enabled = False
self.progress_enabled = True self.progress_enabled = True
self.default_openai_realtime_session = { self.default_openai_realtime_session = {
@ -151,6 +157,12 @@ class VoiceConfig:
} }
self.openai_realtime_session: dict[str, Any] = {} self.openai_realtime_session: dict[str, Any] = {}
@property
def barge_in_enabled(self):
# Later on we may want to tie this value
# to the availability of the webrtcvad package.
return self._barge_in_enabled
def get_session_dict(self): def get_session_dict(self):
return dict(self.default_openai_realtime_session) return dict(self.default_openai_realtime_session)
@ -765,11 +777,11 @@ async def flow_as_tool_websocket(
vad_queue: asyncio.Queue = asyncio.Queue() vad_queue: asyncio.Queue = asyncio.Queue()
vad_audio_buffer = bytearray() vad_audio_buffer = bytearray()
bot_speaking_flag = [False] bot_speaking_flag = [False]
vad = webrtcvad.Vad(mode=3)
async def process_vad_audio() -> None: async def process_vad_audio() -> None:
nonlocal vad_audio_buffer nonlocal vad_audio_buffer
last_speech_time = datetime.now(tz=timezone.utc) last_speech_time = datetime.now(tz=timezone.utc)
vad = get_vad()
while True: while True:
base64_data = await vad_queue.get() base64_data = await vad_queue.get()
raw_chunk_24k = base64.b64decode(base64_data) raw_chunk_24k = base64.b64decode(base64_data)

View file

@ -21,7 +21,6 @@ export interface ConfigResponse {
webhook_polling_interval: number; webhook_polling_interval: number;
serialization_max_items_length: number; serialization_max_items_length: number;
event_delivery: EventDeliveryType; event_delivery: EventDeliveryType;
voice_mode_enabled: boolean;
} }
export const useGetConfig: useQueryFunctionType<undefined, ConfigResponse> = ( export const useGetConfig: useQueryFunctionType<undefined, ConfigResponse> = (
@ -45,9 +44,7 @@ export const useGetConfig: useQueryFunctionType<undefined, ConfigResponse> = (
(state) => state.setWebhookPollingInterval, (state) => state.setWebhookPollingInterval,
); );
const setEventDelivery = useUtilityStore((state) => state.setEventDelivery); const setEventDelivery = useUtilityStore((state) => state.setEventDelivery);
const setVoiceModeEnabled = useFlowsManagerStore(
(state) => state.setVoiceModeEnabled,
);
const { query } = UseRequestProcessor(); const { query } = UseRequestProcessor();
const getConfigFn = async () => { const getConfigFn = async () => {
@ -69,7 +66,6 @@ export const useGetConfig: useQueryFunctionType<undefined, ConfigResponse> = (
data.webhook_polling_interval ?? DEFAULT_POLLING_INTERVAL, data.webhook_polling_interval ?? DEFAULT_POLLING_INTERVAL,
); );
setEventDelivery(data.event_delivery ?? EventDeliveryType.POLLING); setEventDelivery(data.event_delivery ?? EventDeliveryType.POLLING);
setVoiceModeEnabled(Boolean(data.voice_mode_enabled));
} }
return data; return data;
}; };

View file

@ -1,7 +1,6 @@
import ForwardedIconComponent from "@/components/common/genericIconComponent"; import ForwardedIconComponent from "@/components/common/genericIconComponent";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ICON_STROKE_WIDTH } from "@/constants/constants"; import { ICON_STROKE_WIDTH } from "@/constants/constants";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { useVoiceStore } from "@/stores/voiceStore"; import { useVoiceStore } from "@/stores/voiceStore";
interface VoiceButtonProps { interface VoiceButtonProps {
@ -12,13 +11,6 @@ const VoiceButton = ({ toggleRecording }: VoiceButtonProps) => {
const setNewSessionCloseVoiceAssistant = useVoiceStore( const setNewSessionCloseVoiceAssistant = useVoiceStore(
(state) => state.setNewSessionCloseVoiceAssistant, (state) => state.setNewSessionCloseVoiceAssistant,
); );
const voiceModeEnabled = useFlowsManagerStore(
(state) => state.voiceModeEnabled,
);
if (!voiceModeEnabled) {
return null;
}
return ( return (
<> <>

View file

@ -17,8 +17,6 @@ const past = {};
const future = {}; const future = {};
const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
voiceModeEnabled: false,
setVoiceModeEnabled: (voiceModeEnabled: boolean) => set({ voiceModeEnabled }),
IOModalOpen: false, IOModalOpen: false,
setIOModalOpen: (IOModalOpen: boolean) => { setIOModalOpen: (IOModalOpen: boolean) => {
set({ IOModalOpen }); set({ IOModalOpen });

View file

@ -29,8 +29,6 @@ export type FlowsManagerStoreType = {
IOModalOpen: boolean; IOModalOpen: boolean;
setIOModalOpen: (IOModalOpen: boolean) => void; setIOModalOpen: (IOModalOpen: boolean) => void;
resetStore: () => void; resetStore: () => void;
voiceModeEnabled: boolean;
setVoiceModeEnabled: (voiceModeEnabled: boolean) => void;
}; };
export type UseUndoRedoOptions = { export type UseUndoRedoOptions = {