Thành phần quan trọng nhất ở đây là phần phụ trợ Mô hình Ngôn ngữ Lớn (LLM), mà chúng tôi sẽ sử dụng Ollama. được công nhận rộng rãi là một công cụ phổ biến để chạy và phục vụ LLM ngoại tuyến. Nếu Ollama là người mới đối với bạn, tôi khuyên bạn nên xem bài viết trước của tôi về RAG ngoại tuyến: Về cơ bản, bạn chỉ cần tải xuống ứng dụng Ollama, lấy mô hình ưa thích của bạn và chạy nó.
Sơ đồ trình tự cho trợ lý giọng nói với Whisper, Ollama và Bark.
Quá trình triển khai bắt đầu bằng việc tạo TextToSpeechService
dựa trên Bark, kết hợp các phương pháp tổng hợp giọng nói từ văn bản và xử lý các văn bản đầu vào dài hơn một cách liền mạch như sau:
import nltk import torch import warnings import numpy as np from transformers import AutoProcessor, BarkModel warnings.filterwarnings( "ignore", message="torch.nn.utils.weight_norm is deprecated in favor of torch.nn.utils.parametrizations.weight_norm.", ) class TextToSpeechService: def __init__(self, device: str = "cuda" if torch.cuda.is_available() else "cpu"): """ Initializes the TextToSpeechService class. Args: device (str, optional): The device to be used for the model, either "cuda" if a GPU is available or "cpu". Defaults to "cuda" if available, otherwise "cpu". """ self.device = device self.processor = AutoProcessor.from_pretrained("suno/bark-small") self.model = BarkModel.from_pretrained("suno/bark-small") self.model.to(self.device) def synthesize(self, text: str, voice_preset: str = "v2/en_speaker_1"): """ Synthesizes audio from the given text using the specified voice preset. Args: text (str): The input text to be synthesized. voice_preset (str, optional): The voice preset to be used for the synthesis. Defaults to "v2/en_speaker_1". Returns: tuple: A tuple containing the sample rate and the generated audio array. """ inputs = self.processor(text, voice_preset=voice_preset, return_tensors="pt") inputs = {k: v.to(self.device) for k, v in inputs.items()} with torch.no_grad(): audio_array = self.model.generate(**inputs, pad_token_id=10000) audio_array = audio_array.cpu().numpy().squeeze() sample_rate = self.model.generation_config.sample_rate return sample_rate, audio_array def long_form_synthesize(self, text: str, voice_preset: str = "v2/en_speaker_1"): """ Synthesizes audio from the given long-form text using the specified voice preset. Args: text (str): The input text to be synthesized. voice_preset (str, optional): The voice preset to be used for the synthesis. Defaults to "v2/en_speaker_1". Returns: tuple: A tuple containing the sample rate and the generated audio array. """ pieces = [] sentences = nltk.sent_tokenize(text) silence = np.zeros(int(0.25 * self.model.generation_config.sample_rate)) for sent in sentences: sample_rate, audio_array = self.synthesize(sent, voice_preset) pieces += [audio_array, silence.copy()] return self.model.generation_config.sample_rate, np.concatenate(pieces)
__init__
) : Lớp này lấy một tham số device
tùy chọn, chỉ định thiết bị sẽ được sử dụng cho kiểu máy ( cuda
nếu có GPU hoặc cpu
). Nó tải mô hình Bark và bộ xử lý tương ứng từ mô hình được đào tạo trước suno/bark-small
. Bạn cũng có thể sử dụng phiên bản lớn bằng cách chỉ định suno/bark
cho trình tải mô hình.
synthesize
) : Phương thức này lấy đầu vào text
và tham số voice_preset
, chỉ định giọng nói sẽ được sử dụng để tổng hợp. Bạn có thể kiểm tra giá trị voice_preset
khác . Nó sử dụng processor
để chuẩn bị văn bản đầu vào và cài sẵn giọng nói, sau đó tạo mảng âm thanh bằng phương thức model.generate()
. Mảng âm thanh được tạo sẽ được chuyển đổi thành mảng NumPy và tốc độ mẫu được trả về cùng với mảng âm thanh.
long_form_synthesize
) : Phương pháp này được sử dụng để tổng hợp các đầu vào văn bản dài hơn. Đầu tiên, nó mã hóa văn bản đầu vào thành các câu bằng cách sử dụng hàm nltk.sent_tokenize
. Với mỗi câu, nó gọi phương thức synthesize
để tạo ra mảng âm thanh. Sau đó, nó nối các mảng âm thanh được tạo ra với một khoảng im lặng ngắn (0,25 giây) được thêm vào giữa mỗi câu.
Bây giờ chúng ta đã thiết lập TextToSpeechService
, chúng ta cần chuẩn bị máy chủ Ollama để phân phối mô hình ngôn ngữ lớn (LLM). Để thực hiện việc này, bạn cần thực hiện theo các bước sau:
ollama pull llama2
.
ollama serve
.
base.en
) để sao chép dữ liệu đầu vào của người dùng.
ConversationalChain
tích hợp sẵn từ thư viện Langchain, cung cấp mẫu để quản lý luồng hội thoại. Chúng tôi sẽ định cấu hình nó để sử dụng mô hình ngôn ngữ Llama-2 với phần phụ trợ Ollama. import time import threading import numpy as np import whisper import sounddevice as sd from queue import Queue from rich.console import Console from langchain.memory import ConversationBufferMemory from langchain.chains import ConversationChain from langchain.prompts import PromptTemplate from langchain_community.llms import Ollama from tts import TextToSpeechService console = Console() stt = whisper.load_model("base.en") tts = TextToSpeechService() template = """ You are a helpful and friendly AI assistant. You are polite, respectful, and aim to provide concise responses of less than 20 words. The conversation transcript is as follows: {history} And here is the user's follow-up: {input} Your response: """ PROMPT = PromptTemplate(input_variables=["history", "input"], template=template) chain = ConversationChain( prompt=PROMPT, verbose=False, memory=ConversationBufferMemory(ai_prefix="Assistant:"), llm=Ollama(), )
Bây giờ, hãy xác định các chức năng cần thiết:record_audio
: Hàm này chạy trong một luồng riêng biệt để thu thập dữ liệu âm thanh từ micrô của người dùng bằng cách sử dụng sounddevice.RawInputStream
. Hàm gọi lại được gọi bất cứ khi nào có dữ liệu âm thanh mới và đưa dữ liệu vào data_queue
để xử lý thêm.
transcribe
: Hàm này sử dụng phiên bản Whisper để phiên âm dữ liệu âm thanh từ data_queue
thành văn bản.
get_llm_response
: Hàm này cung cấp ngữ cảnh hội thoại hiện tại cho mô hình ngôn ngữ Llama-2 (thông qua Langchain ConversationalChain
) và truy xuất phản hồi văn bản đã tạo.
play_audio
: Chức năng này lấy dạng sóng âm thanh được tạo bởi công cụ chuyển văn bản thành giọng nói Bark và phát lại cho người dùng bằng thư viện phát lại âm thanh (ví dụ: sounddevice
). def record_audio(stop_event, data_queue): """ Captures audio data from the user's microphone and adds it to a queue for further processing. Args: stop_event (threading.Event): An event that, when set, signals the function to stop recording. data_queue (queue.Queue): A queue to which the recorded audio data will be added. Returns: None """ def callback(indata, frames, time, status): if status: console.print(status) data_queue.put(bytes(indata)) with sd.RawInputStream( samplerate=16000, dtype="int16", channels=1, callback=callback ): while not stop_event.is_set(): time.sleep(0.1) def transcribe(audio_np: np.ndarray) -> str: """ Transcribes the given audio data using the Whisper speech recognition model. Args: audio_np (numpy.ndarray): The audio data to be transcribed. Returns: str: The transcribed text. """ result = stt.transcribe(audio_np, fp16=False) # Set fp16=True if using a GPU text = result["text"].strip() return text def get_llm_response(text: str) -> str: """ Generates a response to the given text using the Llama-2 language model. Args: text (str): The input text to be processed. Returns: str: The generated response. """ response = chain.predict(input=text) if response.startswith("Assistant:"): response = response[len("Assistant:") :].strip() return response def play_audio(sample_rate, audio_array): """ Plays the given audio data using the sounddevice library. Args: sample_rate (int): The sample rate of the audio data. audio_array (numpy.ndarray): The audio data to be played. Returns: None """ sd.play(audio_array, sample_rate) sd.wait()
Sau đó, chúng tôi xác định vòng lặp ứng dụng chính. Vòng lặp ứng dụng chính hướng dẫn người dùng thực hiện tương tác đàm thoại như sau:
Sau khi người dùng nhấn Enter, hàm record_audio
sẽ được gọi trong một chuỗi riêng biệt để thu âm thanh đầu vào của người dùng.
Khi người dùng nhấn Enter lần nữa để dừng ghi, dữ liệu âm thanh sẽ được phiên âm bằng chức năng transcribe
.
Sau đó, văn bản được phiên âm sẽ được chuyển đến hàm get_llm_response
, hàm này tạo ra phản hồi bằng mô hình ngôn ngữ Llama-2.
Phản hồi được tạo sẽ được in ra bảng điều khiển và phát lại cho người dùng bằng hàm play_audio
.
if __name__ == "__main__": console.print("[cyan]Assistant started! Press Ctrl+C to exit.") try: while True: console.input( "Press Enter to start recording, then press Enter again to stop." ) data_queue = Queue() # type: ignore[var-annotated] stop_event = threading.Event() recording_thread = threading.Thread( target=record_audio, args=(stop_event, data_queue), ) recording_thread.start() input() stop_event.set() recording_thread.join() audio_data = b"".join(list(data_queue.queue)) audio_np = ( np.frombuffer(audio_data, dtype=np.int16).astype(np.float32) / 32768.0 ) if audio_np.size > 0: with console.status("Transcribing...", spinner="earth"): text = transcribe(audio_np) console.print(f"[yellow]You: {text}") with console.status("Generating response...", spinner="earth"): response = get_llm_response(text) sample_rate, audio_array = tts.long_form_synthesize(response) console.print(f"[cyan]Assistant: {response}") play_audio(sample_rate, audio_array) else: console.print( "[red]No audio recorded. Please ensure your microphone is working." ) except KeyboardInterrupt: console.print("\n[red]Exiting...") console.print("[blue]Session ended.")
Cũng được xuất bản