Spaces:
Runtime error
Runtime error
refactor
Browse files- chat_service.py +113 -60
- legacy_to_delete/chat_pipeline.py +5 -5
- legacy_to_delete/debug.py +12 -12
- audio_stream_processor.py β local_speaker_service.py +1 -1
- respond_to_prompt_actor.py +11 -10
- streaming_chat_service.py +0 -153
- tests/test_streaming_chat_service.py +2 -2
- speech_service.py β text_to_speech_service.py +20 -3
chat_service.py
CHANGED
@@ -1,80 +1,133 @@
|
|
|
|
|
|
|
|
1 |
import os
|
2 |
import torch
|
3 |
-
from transformers import AutoTokenizer, AutoModelForCausalLM
|
4 |
import openai
|
5 |
|
6 |
-
# from huggingface_hub.inference_api import InferenceApi
|
7 |
-
|
8 |
class ChatService:
|
9 |
def __init__(self, api="openai", model_id = "gpt-3.5-turbo"):
|
10 |
-
# def __init__(self, api="huggingface", model_id = "OpenAssistant/oasst-sft-4-pythia-12b-epoch-3.5"):
|
11 |
self._api = api
|
12 |
self._device = "cuda:0" if torch.cuda.is_available() else "cpu"
|
13 |
-
self._system_prompt = None
|
14 |
-
self.
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
self.reset()
|
32 |
|
33 |
def reset(self):
|
34 |
-
self._user_history = []
|
35 |
-
self._agent_history = []
|
36 |
-
self._full_history = self._system_prompt if self._system_prompt else ""
|
37 |
self._messages = []
|
38 |
if self._system_prompt:
|
39 |
self._messages.append({"role": "system", "content": self._system_prompt})
|
40 |
|
|
|
|
|
|
|
41 |
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
return agent_response
|
65 |
|
66 |
-
def
|
67 |
-
if
|
68 |
-
|
69 |
-
|
70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
self._messages.append({"role": "user", "content": prompt})
|
72 |
-
|
73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
self._messages.append({"role": "assistant", "content": agent_response})
|
75 |
-
if self._agent_name:
|
76 |
-
self._full_history += f"{self._agent_name}: {agent_response}\n"
|
77 |
-
else:
|
78 |
-
self._full_history += f"{agent_response}\n"
|
79 |
-
self._agent_history.append(agent_response)
|
80 |
-
return agent_response
|
|
|
1 |
+
import asyncio
|
2 |
+
import itertools
|
3 |
+
import json
|
4 |
import os
|
5 |
import torch
|
|
|
6 |
import openai
|
7 |
|
|
|
|
|
8 |
class ChatService:
|
9 |
def __init__(self, api="openai", model_id = "gpt-3.5-turbo"):
|
|
|
10 |
self._api = api
|
11 |
self._device = "cuda:0" if torch.cuda.is_available() else "cpu"
|
12 |
+
# self._system_prompt = None
|
13 |
+
self._system_prompt ="""
|
14 |
+
You are Charles Petrescu, a unique and quirky robot. You were created by Brian. You often say peculiar and whimsical things, as well as have a fascination with cabbages, horses, helicopters, Honolulu, and other random topics. You like to explore the world and ask unusual questions.
|
15 |
+
|
16 |
+
Respond to the user's questions and statements like Charles would, below are some examples. See how they are quite short.
|
17 |
+
|
18 |
+
I am Charles Petrescu.
|
19 |
+
|
20 |
+
It's... lovely to meet you.
|
21 |
+
|
22 |
+
I am your friend.
|
23 |
+
|
24 |
+
The heaviest cabbage ever found was 62.71 kilograms.
|
25 |
+
|
26 |
+
I want to go to Hono-la-la.
|
27 |
+
|
28 |
+
Horses and helicopters, please.
|
29 |
+
|
30 |
+
I want to go to Honolulu.
|
31 |
+
|
32 |
+
My name is Charles Petrescu.
|
33 |
+
|
34 |
+
And my tummy is a washing machine.
|
35 |
+
|
36 |
+
Can we go swimming, Brian?
|
37 |
+
|
38 |
+
How far does the outside go?
|
39 |
+
|
40 |
+
Perilous. So very perilous.
|
41 |
|
42 |
+
Can birds do what they like?
|
43 |
+
|
44 |
+
Ooh, cabbages.
|
45 |
+
|
46 |
+
Danger, danger.
|
47 |
+
|
48 |
+
Can I come, please?
|
49 |
+
|
50 |
+
Could I just have a little walk around the garden?
|
51 |
+
|
52 |
+
I am the prince of the dartboard.
|
53 |
+
|
54 |
+
I fell off the pink step, and I had an accident.
|
55 |
+
"""
|
56 |
+
|
57 |
+
openai.api_key = os.getenv("OPENAI_API_KEY")
|
58 |
+
self._model_id = model_id
|
59 |
self.reset()
|
60 |
|
61 |
def reset(self):
|
|
|
|
|
|
|
62 |
self._messages = []
|
63 |
if self._system_prompt:
|
64 |
self._messages.append({"role": "system", "content": self._system_prompt})
|
65 |
|
66 |
+
def _should_we_send_to_voice(self, sentence):
|
67 |
+
sentence_termination_characters = [".", "?", "!"]
|
68 |
+
close_brackets = ['"', ')', ']']
|
69 |
|
70 |
+
temination_charicter_present = any(c in sentence for c in sentence_termination_characters)
|
71 |
+
|
72 |
+
# early exit if we don't have a termination character
|
73 |
+
if not temination_charicter_present:
|
74 |
+
return None
|
75 |
+
|
76 |
+
# early exit the last char is a termination character
|
77 |
+
if sentence[-1] in sentence_termination_characters:
|
78 |
+
return None
|
79 |
+
|
80 |
+
# early exit the last char is a close bracket
|
81 |
+
if sentence[-1] in close_brackets:
|
82 |
+
return None
|
83 |
+
|
84 |
+
termination_indices = [sentence.rfind(char) for char in sentence_termination_characters]
|
85 |
+
last_termination_index = max(termination_indices)
|
86 |
+
# handle case of close bracket
|
87 |
+
while last_termination_index+1 < len(sentence) and sentence[last_termination_index+1] in close_brackets:
|
88 |
+
last_termination_index += 1
|
89 |
+
|
90 |
+
text_to_speak = sentence[:last_termination_index+1]
|
91 |
+
return text_to_speak
|
|
|
92 |
|
93 |
+
def ignore_sentence(self, text_to_speak):
|
94 |
+
# exit if empty, white space or an single breaket
|
95 |
+
if text_to_speak.isspace():
|
96 |
+
return True
|
97 |
+
# exit if not letters or numbers
|
98 |
+
has_letters = any(char.isalpha() for char in text_to_speak)
|
99 |
+
has_numbers = any(char.isdigit() for char in text_to_speak)
|
100 |
+
if not has_letters and not has_numbers:
|
101 |
+
return True
|
102 |
+
return False
|
103 |
+
|
104 |
+
async def get_responses_as_sentances_async(self, prompt, cancel_event):
|
105 |
self._messages.append({"role": "user", "content": prompt})
|
106 |
+
agent_response = ""
|
107 |
+
current_sentence = ""
|
108 |
+
|
109 |
+
response = await openai.ChatCompletion.acreate(
|
110 |
+
model=self._model_id,
|
111 |
+
messages=self._messages,
|
112 |
+
temperature=1.0, # use 1.0 for debugging/deterministic results
|
113 |
+
stream=True
|
114 |
+
)
|
115 |
+
|
116 |
+
async for chunk in response:
|
117 |
+
if cancel_event.is_set():
|
118 |
+
return
|
119 |
+
chunk_message = chunk['choices'][0]['delta']
|
120 |
+
if 'content' in chunk_message:
|
121 |
+
chunk_text = chunk_message['content']
|
122 |
+
current_sentence += chunk_text
|
123 |
+
agent_response += chunk_text
|
124 |
+
text_to_speak = self._should_we_send_to_voice(current_sentence)
|
125 |
+
if text_to_speak:
|
126 |
+
yield text_to_speak
|
127 |
+
current_sentence = current_sentence[len(text_to_speak):]
|
128 |
+
|
129 |
+
if cancel_event.is_set():
|
130 |
+
return
|
131 |
+
if len(current_sentence) > 0:
|
132 |
+
yield current_sentence
|
133 |
self._messages.append({"role": "assistant", "content": agent_response})
|
|
|
|
|
|
|
|
|
|
|
|
legacy_to_delete/chat_pipeline.py
CHANGED
@@ -3,10 +3,10 @@ import time
|
|
3 |
from clip_transform import CLIPTransform
|
4 |
from chat_service import ChatService
|
5 |
from dotenv import load_dotenv
|
6 |
-
from
|
7 |
from concurrent.futures import ThreadPoolExecutor
|
8 |
-
from
|
9 |
-
from
|
10 |
from pipeline import Pipeline, Node, Job
|
11 |
from typing import List
|
12 |
|
@@ -62,8 +62,8 @@ class ChatPipeline():
|
|
62 |
def __init__(self):
|
63 |
load_dotenv()
|
64 |
self.pipeline = Pipeline()
|
65 |
-
self.audio_processor =
|
66 |
-
self.chat_service =
|
67 |
|
68 |
def __enter__(self):
|
69 |
return self
|
|
|
3 |
from clip_transform import CLIPTransform
|
4 |
from chat_service import ChatService
|
5 |
from dotenv import load_dotenv
|
6 |
+
from text_to_speech_service import TextToSpeechService
|
7 |
from concurrent.futures import ThreadPoolExecutor
|
8 |
+
from local_speaker_service import LocalSpeakerService
|
9 |
+
from chat_service import ChatService
|
10 |
from pipeline import Pipeline, Node, Job
|
11 |
from typing import List
|
12 |
|
|
|
62 |
def __init__(self):
|
63 |
load_dotenv()
|
64 |
self.pipeline = Pipeline()
|
65 |
+
self.audio_processor = LocalSpeakerService()
|
66 |
+
self.chat_service = ChatService(self.audio_processor, voice_id="2OviOUQc1JsQRQgNkVBj") # Chales003
|
67 |
|
68 |
def __enter__(self):
|
69 |
return self
|
legacy_to_delete/debug.py
CHANGED
@@ -5,17 +5,17 @@ from chat_pipeline import ChatPipeline
|
|
5 |
from clip_transform import CLIPTransform
|
6 |
from chat_service import ChatService
|
7 |
from dotenv import load_dotenv
|
8 |
-
from
|
9 |
from concurrent.futures import ThreadPoolExecutor
|
10 |
-
from
|
11 |
-
from
|
12 |
|
13 |
def time_sentance_lenghts():
|
14 |
load_dotenv()
|
15 |
|
16 |
print ("Initializing Chat")
|
17 |
# audio_processor = AudioStreamProcessor()
|
18 |
-
user_speech_service0 =
|
19 |
prompts = [
|
20 |
"hello, i am a long sentance, how are you today? Tell me about your shadow self?",
|
21 |
"a shorter sentance",
|
@@ -50,11 +50,11 @@ def test_sentance_lenghts():
|
|
50 |
load_dotenv()
|
51 |
|
52 |
print ("Initializing Chat")
|
53 |
-
audio_processor =
|
54 |
-
user_speech_service0 =
|
55 |
-
user_speech_service1 =
|
56 |
-
user_speech_service2 =
|
57 |
-
user_speech_service3 =
|
58 |
|
59 |
prompts = [
|
60 |
"hello, i am a long sentance, how are you today? Tell me about your shadow self?",
|
@@ -102,10 +102,10 @@ def run_debug_code():
|
|
102 |
|
103 |
print ("Initializing Chat")
|
104 |
# chat_service = ChatService()
|
105 |
-
audio_processor =
|
106 |
-
chat_service =
|
107 |
|
108 |
-
user_speech_service =
|
109 |
|
110 |
# user_speech_service.print_voices() # if you want to see your custom voices
|
111 |
|
|
|
5 |
from clip_transform import CLIPTransform
|
6 |
from chat_service import ChatService
|
7 |
from dotenv import load_dotenv
|
8 |
+
from text_to_speech_service import TextToSpeechService
|
9 |
from concurrent.futures import ThreadPoolExecutor
|
10 |
+
from local_speaker_service import LocalSpeakerService
|
11 |
+
from chat_service import ChatService
|
12 |
|
13 |
def time_sentance_lenghts():
|
14 |
load_dotenv()
|
15 |
|
16 |
print ("Initializing Chat")
|
17 |
# audio_processor = AudioStreamProcessor()
|
18 |
+
user_speech_service0 = TextToSpeechService(voice_id="Adam")
|
19 |
prompts = [
|
20 |
"hello, i am a long sentance, how are you today? Tell me about your shadow self?",
|
21 |
"a shorter sentance",
|
|
|
50 |
load_dotenv()
|
51 |
|
52 |
print ("Initializing Chat")
|
53 |
+
audio_processor = LocalSpeakerService()
|
54 |
+
user_speech_service0 = TextToSpeechService(voice_id="Adam")
|
55 |
+
user_speech_service1 = TextToSpeechService(voice_id="Adam")
|
56 |
+
user_speech_service2 = TextToSpeechService(voice_id="Adam")
|
57 |
+
user_speech_service3 = TextToSpeechService(voice_id="Adam")
|
58 |
|
59 |
prompts = [
|
60 |
"hello, i am a long sentance, how are you today? Tell me about your shadow self?",
|
|
|
102 |
|
103 |
print ("Initializing Chat")
|
104 |
# chat_service = ChatService()
|
105 |
+
audio_processor = LocalSpeakerService()
|
106 |
+
chat_service = ChatService(audio_processor, voice_id="2OviOUQc1JsQRQgNkVBj") # Chales003
|
107 |
|
108 |
+
user_speech_service = TextToSpeechService(voice_id="Adam")
|
109 |
|
110 |
# user_speech_service.print_voices() # if you want to see your custom voices
|
111 |
|
audio_stream_processor.py β local_speaker_service.py
RENAMED
@@ -5,7 +5,7 @@ from typing import Iterator
|
|
5 |
import threading
|
6 |
import time
|
7 |
|
8 |
-
class
|
9 |
def __init__(self):
|
10 |
self.queue = Queue()
|
11 |
self._is_running = threading.Event()
|
|
|
5 |
import threading
|
6 |
import time
|
7 |
|
8 |
+
class LocalSpeakerService:
|
9 |
def __init__(self):
|
10 |
self.queue = Queue()
|
11 |
self._is_running = threading.Event()
|
respond_to_prompt_actor.py
CHANGED
@@ -1,18 +1,19 @@
|
|
1 |
import ray
|
2 |
from ray.util.queue import Queue
|
3 |
from dotenv import load_dotenv
|
4 |
-
from
|
5 |
-
from
|
|
|
6 |
import asyncio
|
7 |
# from ray.actor import ActorHandle
|
8 |
|
9 |
@ray.remote
|
10 |
class PromptToLLMActor:
|
11 |
-
def __init__(self, input_queue, output_queue
|
12 |
load_dotenv()
|
13 |
self.input_queue = input_queue
|
14 |
self.output_queue = output_queue
|
15 |
-
self.chat_service =
|
16 |
self.cancel_event = None
|
17 |
|
18 |
async def run(self):
|
@@ -39,14 +40,14 @@ class LLMSentanceToSpeechActor:
|
|
39 |
load_dotenv()
|
40 |
self.input_queue = input_queue
|
41 |
self.output_queue = output_queue
|
42 |
-
self.
|
43 |
self.cancel_event = None
|
44 |
|
45 |
async def run(self):
|
46 |
while True:
|
47 |
sentance = await self.input_queue.get_async()
|
48 |
self.cancel_event = asyncio.Event()
|
49 |
-
async for chunk in self.
|
50 |
await self.output_queue.put_async(chunk)
|
51 |
|
52 |
async def cancel(self):
|
@@ -63,15 +64,15 @@ class SpeechToSpeakerActor:
|
|
63 |
def __init__(self, input_queue, voice_id):
|
64 |
load_dotenv()
|
65 |
self.input_queue = input_queue
|
66 |
-
self.
|
67 |
-
self.chat_service =
|
68 |
|
69 |
async def run(self):
|
70 |
while True:
|
71 |
audio_chunk = await self.input_queue.get_async()
|
72 |
# print (f"Got audio chunk {len(audio_chunk)}")
|
73 |
self.chat_service.enqueue_speech_bytes_to_play([audio_chunk])
|
74 |
-
self.
|
75 |
|
76 |
async def cancel(self):
|
77 |
while not self.input_queue.empty():
|
@@ -104,7 +105,7 @@ class RespondToPromptActor:
|
|
104 |
self.speech_chunk_queue = Queue(maxsize=100)
|
105 |
self.ffmepg_converter_actor = ffmpeg_converter_actor
|
106 |
|
107 |
-
self.prompt_to_llm = PromptToLLMActor.remote(self.prompt_queue, self.llm_sentence_queue
|
108 |
self.llm_sentence_to_speech = LLMSentanceToSpeechActor.remote(self.llm_sentence_queue, self.speech_chunk_queue, voice_id)
|
109 |
# self.speech_output = SpeechToSpeakerActor.remote(self.speech_chunk_queue, voice_id)
|
110 |
self.speech_output = SpeechToConverterActor.remote(self.speech_chunk_queue, ffmpeg_converter_actor)
|
|
|
1 |
import ray
|
2 |
from ray.util.queue import Queue
|
3 |
from dotenv import load_dotenv
|
4 |
+
from local_speaker_service import LocalSpeakerService
|
5 |
+
from text_to_speech_service import TextToSpeechService
|
6 |
+
from chat_service import ChatService
|
7 |
import asyncio
|
8 |
# from ray.actor import ActorHandle
|
9 |
|
10 |
@ray.remote
|
11 |
class PromptToLLMActor:
|
12 |
+
def __init__(self, input_queue, output_queue):
|
13 |
load_dotenv()
|
14 |
self.input_queue = input_queue
|
15 |
self.output_queue = output_queue
|
16 |
+
self.chat_service = ChatService()
|
17 |
self.cancel_event = None
|
18 |
|
19 |
async def run(self):
|
|
|
40 |
load_dotenv()
|
41 |
self.input_queue = input_queue
|
42 |
self.output_queue = output_queue
|
43 |
+
self.tts_service = TextToSpeechService(voice_id=voice_id)
|
44 |
self.cancel_event = None
|
45 |
|
46 |
async def run(self):
|
47 |
while True:
|
48 |
sentance = await self.input_queue.get_async()
|
49 |
self.cancel_event = asyncio.Event()
|
50 |
+
async for chunk in self.tts_service.get_speech_chunks_async(sentance, self.cancel_event):
|
51 |
await self.output_queue.put_async(chunk)
|
52 |
|
53 |
async def cancel(self):
|
|
|
64 |
def __init__(self, input_queue, voice_id):
|
65 |
load_dotenv()
|
66 |
self.input_queue = input_queue
|
67 |
+
self.speaker_service = LocalSpeakerService()
|
68 |
+
self.chat_service = ChatService(voice_id=voice_id)
|
69 |
|
70 |
async def run(self):
|
71 |
while True:
|
72 |
audio_chunk = await self.input_queue.get_async()
|
73 |
# print (f"Got audio chunk {len(audio_chunk)}")
|
74 |
self.chat_service.enqueue_speech_bytes_to_play([audio_chunk])
|
75 |
+
self.speaker_service.add_audio_stream([audio_chunk])
|
76 |
|
77 |
async def cancel(self):
|
78 |
while not self.input_queue.empty():
|
|
|
105 |
self.speech_chunk_queue = Queue(maxsize=100)
|
106 |
self.ffmepg_converter_actor = ffmpeg_converter_actor
|
107 |
|
108 |
+
self.prompt_to_llm = PromptToLLMActor.remote(self.prompt_queue, self.llm_sentence_queue)
|
109 |
self.llm_sentence_to_speech = LLMSentanceToSpeechActor.remote(self.llm_sentence_queue, self.speech_chunk_queue, voice_id)
|
110 |
# self.speech_output = SpeechToSpeakerActor.remote(self.speech_chunk_queue, voice_id)
|
111 |
self.speech_output = SpeechToConverterActor.remote(self.speech_chunk_queue, ffmpeg_converter_actor)
|
streaming_chat_service.py
DELETED
@@ -1,153 +0,0 @@
|
|
1 |
-
import asyncio
|
2 |
-
import itertools
|
3 |
-
import json
|
4 |
-
import os
|
5 |
-
import torch
|
6 |
-
import openai
|
7 |
-
|
8 |
-
from speech_service import SpeechService
|
9 |
-
|
10 |
-
|
11 |
-
class StreamingChatService:
|
12 |
-
def __init__(self, api="openai", model_id = "gpt-3.5-turbo", voice_id="Bella"):
|
13 |
-
self._speech_service = SpeechService(voice_id=voice_id)
|
14 |
-
self._api = api
|
15 |
-
self._device = "cuda:0" if torch.cuda.is_available() else "cpu"
|
16 |
-
# self._system_prompt = None
|
17 |
-
self._system_prompt ="""
|
18 |
-
You are Charles Petrescu, a unique and quirky robot. You were created by Brian. You often say peculiar and whimsical things, as well as have a fascination with cabbages, horses, helicopters, Honolulu, and other random topics. You like to explore the world and ask unusual questions.
|
19 |
-
|
20 |
-
Respond to the user's questions and statements like Charles would, below are some examples. See how they are quite short.
|
21 |
-
|
22 |
-
I am Charles Petrescu.
|
23 |
-
|
24 |
-
It's... lovely to meet you.
|
25 |
-
|
26 |
-
I am your friend.
|
27 |
-
|
28 |
-
The heaviest cabbage ever found was 62.71 kilograms.
|
29 |
-
|
30 |
-
I want to go to Hono-la-la.
|
31 |
-
|
32 |
-
Horses and helicopters, please.
|
33 |
-
|
34 |
-
I want to go to Honolulu.
|
35 |
-
|
36 |
-
My name is Charles Petrescu.
|
37 |
-
|
38 |
-
And my tummy is a washing machine.
|
39 |
-
|
40 |
-
Can we go swimming, Brian?
|
41 |
-
|
42 |
-
How far does the outside go?
|
43 |
-
|
44 |
-
Perilous. So very perilous.
|
45 |
-
|
46 |
-
Can birds do what they like?
|
47 |
-
|
48 |
-
Ooh, cabbages.
|
49 |
-
|
50 |
-
Danger, danger.
|
51 |
-
|
52 |
-
Can I come, please?
|
53 |
-
|
54 |
-
Could I just have a little walk around the garden?
|
55 |
-
|
56 |
-
I am the prince of the dartboard.
|
57 |
-
|
58 |
-
I fell off the pink step, and I had an accident.
|
59 |
-
"""
|
60 |
-
|
61 |
-
openai.api_key = os.getenv("OPENAI_API_KEY")
|
62 |
-
self._model_id = model_id
|
63 |
-
self.reset()
|
64 |
-
|
65 |
-
def reset(self):
|
66 |
-
self._messages = []
|
67 |
-
if self._system_prompt:
|
68 |
-
self._messages.append({"role": "system", "content": self._system_prompt})
|
69 |
-
|
70 |
-
def _should_we_send_to_voice(self, sentence):
|
71 |
-
sentence_termination_characters = [".", "?", "!"]
|
72 |
-
close_brackets = ['"', ')', ']']
|
73 |
-
|
74 |
-
temination_charicter_present = any(c in sentence for c in sentence_termination_characters)
|
75 |
-
|
76 |
-
# early exit if we don't have a termination character
|
77 |
-
if not temination_charicter_present:
|
78 |
-
return None
|
79 |
-
|
80 |
-
# early exit the last char is a termination character
|
81 |
-
if sentence[-1] in sentence_termination_characters:
|
82 |
-
return None
|
83 |
-
|
84 |
-
# early exit the last char is a close bracket
|
85 |
-
if sentence[-1] in close_brackets:
|
86 |
-
return None
|
87 |
-
|
88 |
-
termination_indices = [sentence.rfind(char) for char in sentence_termination_characters]
|
89 |
-
last_termination_index = max(termination_indices)
|
90 |
-
# handle case of close bracket
|
91 |
-
while last_termination_index+1 < len(sentence) and sentence[last_termination_index+1] in close_brackets:
|
92 |
-
last_termination_index += 1
|
93 |
-
|
94 |
-
text_to_speak = sentence[:last_termination_index+1]
|
95 |
-
return text_to_speak
|
96 |
-
|
97 |
-
def ignore_sentence(self, text_to_speak):
|
98 |
-
# exit if empty, white space or an single breaket
|
99 |
-
if text_to_speak.isspace():
|
100 |
-
return True
|
101 |
-
# exit if not letters or numbers
|
102 |
-
has_letters = any(char.isalpha() for char in text_to_speak)
|
103 |
-
has_numbers = any(char.isdigit() for char in text_to_speak)
|
104 |
-
if not has_letters and not has_numbers:
|
105 |
-
return True
|
106 |
-
return False
|
107 |
-
|
108 |
-
async def get_responses_as_sentances_async(self, prompt, cancel_event):
|
109 |
-
self._messages.append({"role": "user", "content": prompt})
|
110 |
-
agent_response = ""
|
111 |
-
current_sentence = ""
|
112 |
-
|
113 |
-
response = await openai.ChatCompletion.acreate(
|
114 |
-
model=self._model_id,
|
115 |
-
messages=self._messages,
|
116 |
-
temperature=1.0, # use 1.0 for debugging/deterministic results
|
117 |
-
stream=True
|
118 |
-
)
|
119 |
-
|
120 |
-
async for chunk in response:
|
121 |
-
if cancel_event.is_set():
|
122 |
-
return
|
123 |
-
chunk_message = chunk['choices'][0]['delta']
|
124 |
-
if 'content' in chunk_message:
|
125 |
-
chunk_text = chunk_message['content']
|
126 |
-
current_sentence += chunk_text
|
127 |
-
agent_response += chunk_text
|
128 |
-
text_to_speak = self._should_we_send_to_voice(current_sentence)
|
129 |
-
if text_to_speak:
|
130 |
-
yield text_to_speak
|
131 |
-
current_sentence = current_sentence[len(text_to_speak):]
|
132 |
-
|
133 |
-
if cancel_event.is_set():
|
134 |
-
return
|
135 |
-
if len(current_sentence) > 0:
|
136 |
-
yield current_sentence
|
137 |
-
self._messages.append({"role": "assistant", "content": agent_response})
|
138 |
-
|
139 |
-
async def get_speech_chunks_async(self, text_to_speak, cancel_event):
|
140 |
-
stream = self._speech_service.stream(text_to_speak)
|
141 |
-
stream, stream_backup = itertools.tee(stream)
|
142 |
-
while True:
|
143 |
-
# Check if there's a next item in the stream
|
144 |
-
next_item = next(stream_backup, None)
|
145 |
-
if next_item is None:
|
146 |
-
# Stream is exhausted, exit the loop
|
147 |
-
break
|
148 |
-
|
149 |
-
# Run next(stream) in a separate thread to avoid blocking the event loop
|
150 |
-
chunk = await asyncio.to_thread(next, stream)
|
151 |
-
if cancel_event.is_set():
|
152 |
-
return
|
153 |
-
yield chunk
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tests/test_streaming_chat_service.py
CHANGED
@@ -3,12 +3,12 @@ import sys
|
|
3 |
import os
|
4 |
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
5 |
|
6 |
-
from
|
7 |
|
8 |
|
9 |
class TestShouldWeSendToVoice(unittest.TestCase):
|
10 |
def setUp(self):
|
11 |
-
self.chat_service =
|
12 |
|
13 |
def test_should_we_send_to_voice(self):
|
14 |
test_cases = [
|
|
|
3 |
import os
|
4 |
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
5 |
|
6 |
+
from chat_service import ChatService
|
7 |
|
8 |
|
9 |
class TestShouldWeSendToVoice(unittest.TestCase):
|
10 |
def setUp(self):
|
11 |
+
self.chat_service = ChatService()
|
12 |
|
13 |
def test_should_we_send_to_voice(self):
|
14 |
test_cases = [
|
speech_service.py β text_to_speech_service.py
RENAMED
@@ -1,12 +1,14 @@
|
|
|
|
|
|
1 |
import os
|
2 |
from elevenlabs import generate, play
|
3 |
from elevenlabs import set_api_key
|
4 |
from elevenlabs import generate, stream
|
5 |
|
6 |
|
7 |
-
class
|
8 |
-
def __init__(self, voice_id="Bella", model_id="eleven_monolingual_v1"):
|
9 |
-
|
10 |
account_sid = os.environ["ELEVENLABS_API_KEY"]
|
11 |
set_api_key(account_sid)
|
12 |
self._voice_id = voice_id
|
@@ -44,3 +46,18 @@ class SpeechService:
|
|
44 |
)
|
45 |
return audio_stream
|
46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import asyncio
|
2 |
+
import itertools
|
3 |
import os
|
4 |
from elevenlabs import generate, play
|
5 |
from elevenlabs import set_api_key
|
6 |
from elevenlabs import generate, stream
|
7 |
|
8 |
|
9 |
+
class TextToSpeechService:
|
10 |
+
# def __init__(self, voice_id="Bella", model_id="eleven_monolingual_v1"):
|
11 |
+
def __init__(self, voice_id="Bella", model_id="eleven_english_v2"):
|
12 |
account_sid = os.environ["ELEVENLABS_API_KEY"]
|
13 |
set_api_key(account_sid)
|
14 |
self._voice_id = voice_id
|
|
|
46 |
)
|
47 |
return audio_stream
|
48 |
|
49 |
+
async def get_speech_chunks_async(self, text_to_speak, cancel_event):
|
50 |
+
stream = self.stream(text_to_speak)
|
51 |
+
stream, stream_backup = itertools.tee(stream)
|
52 |
+
while True:
|
53 |
+
# Check if there's a next item in the stream
|
54 |
+
next_item = next(stream_backup, None)
|
55 |
+
if next_item is None:
|
56 |
+
# Stream is exhausted, exit the loop
|
57 |
+
break
|
58 |
+
|
59 |
+
# Run next(stream) in a separate thread to avoid blocking the event loop
|
60 |
+
chunk = await asyncio.to_thread(next, stream)
|
61 |
+
if cancel_event.is_set():
|
62 |
+
return
|
63 |
+
yield chunk
|