Skip to content

Commit

Permalink
Merge pull request #238 from bolna-ai/feat/fillers
Browse files Browse the repository at this point in the history
Feat/fillers
  • Loading branch information
marmikcfc authored Jun 10, 2024
2 parents d98aaea + ba7d6bb commit e5da076
Show file tree
Hide file tree
Showing 12 changed files with 165 additions and 77 deletions.
6 changes: 3 additions & 3 deletions bolna/agent_manager/assistant_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

class AssistantManager(BaseManager):
def __init__(self, agent_config, ws=None, assistant_id=None, context_data=None, conversation_history=None,
connected_through_dashboard=None, cache=None, input_queue=None, output_queue=None, **kwargs):
turn_based_conversation=None, cache=None, input_queue=None, output_queue=None, **kwargs):
super().__init__()
self.tools = {}
self.websocket = ws
Expand All @@ -20,7 +20,7 @@ def __init__(self, agent_config, ws=None, assistant_id=None, context_data=None,
self.task_states = [False] * len(self.tasks)
self.assistant_id = assistant_id
self.run_id = f"{self.assistant_id}#{str(int(time.time() * 1000))}"
self.connected_through_dashboard = connected_through_dashboard
self.turn_based_conversation = turn_based_conversation
self.cache = cache
self.input_queue = input_queue
self.output_queue = output_queue
Expand All @@ -42,7 +42,7 @@ async def run(self, local=False, run_id=None):
task_id, task, self.websocket,
context_data=self.context_data, input_parameters=input_parameters,
assistant_id=self.assistant_id, run_id=self.run_id,
connected_through_dashboard=self.connected_through_dashboard,
turn_based_conversation=self.turn_based_conversation,
cache=self.cache, input_queue=self.input_queue, output_queue=self.output_queue,
conversation_history=self.conversation_history, **self.kwargs)
await task_manager.load_prompt(self.agent_config.get("agent_name", self.agent_config.get("assistant_name")),
Expand Down
93 changes: 63 additions & 30 deletions bolna/agent_manager/task_manager.py

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions bolna/classification/classification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from bolna.helpers.logger_config import configure_logger

logger = configure_logger(__name__)


class BaseClassifier:
def __init__(self, model, prompt, labels, threshold = 0.6, multi_label = False):
self.model_name = model
self.prompt = prompt
self.classification_labels = labels
self.multi_label = multi_label
self.threshold = threshold

async def classify(self, messages):
pass
33 changes: 33 additions & 0 deletions bolna/classification/deberta.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@


from dotenv import load_dotenv
from transformers import AutoTokenizer, pipeline
from optimum.onnxruntime import ORTModelForSequenceClassification

from bolna.classification.classification import BaseClassifier
from bolna.helpers.logger_config import configure_logger


logger = configure_logger(__name__)


load_dotenv()


class DeBERTaClassifier(BaseClassifier):
def __init__(self, model_id, prompt, labels, threshold, multi_label=False, filename = None):
super().__init__(model_id, prompt, labels, multi_label, threshold)
self.model_args = { "model_id": self.model_name}
logger.info(f"Creating for {self.model_name}, classifier {model_id}")
if filename is not None:
self.model_args['file_name'] = filename
self.model = ORTModelForSequenceClassification.from_pretrained(**self.model_args)
self.tokenizer = AutoTokenizer.from_pretrained(model_id)
self.tokenizer.model_input_names = ['input_ids', 'attention_mask']
self.classifier = pipeline("zero-shot-classification", model=self.model, tokenizer=self.tokenizer)

def classify(self, text):
output = self.classifier(text, self.classification_labels, multi_label=self.multi_label)
logger.info(f"Most eligible response {output['labels'][0]}")
return output['labels'][0] if output['scores'][0] > self.threshold else None

4 changes: 2 additions & 2 deletions bolna/helpers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def raw_to_mulaw(raw_bytes):
return mulaw_encoded


async def get_s3_file(bucket_name, file_key):
async def get_s3_file(bucket_name = BUCKET_NAME, file_key = ""):
session = AioSession()

async with AsyncExitStack() as exit_stack:
Expand Down Expand Up @@ -379,7 +379,7 @@ def merge_wav_bytes(wav_files_bytes):
return buffer.getvalue()

def calculate_audio_duration(size_bytes, sampling_rate, bit_depth = 16, channels = 1, format = "wav"):
bytes_per_sample = (bit_depth / 8) * channels if format != "mulaw" else 1
bytes_per_sample = (bit_depth / 8) * channels if format != 'mulaw' else 1
total_samples = size_bytes / bytes_per_sample
duration_seconds = total_samples / sampling_rate
return duration_seconds
Expand Down
8 changes: 4 additions & 4 deletions bolna/input_handlers/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@


class DefaultInputHandler:
def __init__(self, queues=None, websocket=None, input_types=None, mark_set = None, queue = None, connected_through_dashboard=False, conversation_recording = None):
def __init__(self, queues=None, websocket=None, input_types=None, mark_set = None, queue = None, turn_based_conversation=False, conversation_recording = None):
self.queues = queues
self.websocket = websocket
self.input_types = input_types
self.websocket_listen_task = None
self.running = True
self.connected_through_dashboard = connected_through_dashboard
self.turn_based_conversation = turn_based_conversation
self.queue = queue
self.conversation_recording = conversation_recording
async def stop_handler(self):
Expand Down Expand Up @@ -53,7 +53,7 @@ def __process_text(self, text):
'sequence': self.input_types['audio']
})

if self.connected_through_dashboard:
if self.turn_based_conversation:
ws_data_packet["meta_info"]["bypass_synth"] = True
self.queues['llm'].put_nowait(ws_data_packet)

Expand Down Expand Up @@ -81,7 +81,7 @@ async def _listen(self):
return

async def process_message(self, message):
if message['type'] not in self.input_types.keys() and not self.connected_through_dashboard:
if message['type'] not in self.input_types.keys() and not self.turn_based_conversation:
logger.info(f"straight away returning")
return {"message": "invalid input type"}

Expand Down
4 changes: 2 additions & 2 deletions bolna/input_handlers/telephony.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@


class TelephonyInputHandler(DefaultInputHandler):
def __init__(self, queues, websocket=None, input_types=None, mark_set=None, connected_through_dashboard=False):
super().__init__(queues, websocket, input_types, connected_through_dashboard)
def __init__(self, queues, websocket=None, input_types=None, mark_set=None, turn_based_conversation=False):
super().__init__(queues, websocket, input_types, turn_based_conversation)
self.stream_sid = None
self.call_sid = None
self.buffer = []
Expand Down
4 changes: 2 additions & 2 deletions bolna/input_handlers/telephony_providers/exotel.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@


class ExotelInputHandler(TelephonyInputHandler):
def __init__(self, queues, websocket=None, input_types=None, mark_set=None, connected_through_dashboard=False):
super().__init__(queues, websocket, input_types, mark_set, connected_through_dashboard)
def __init__(self, queues, websocket=None, input_types=None, mark_set=None, turn_based_conversation=False):
super().__init__(queues, websocket, input_types, mark_set, turn_based_conversation)
self.io_provider = 'exotel'

async def call_start(self, packet):
Expand Down
4 changes: 2 additions & 2 deletions bolna/input_handlers/telephony_providers/plivo.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@


class PlivoInputHandler(TelephonyInputHandler):
def __init__(self, queues, websocket=None, input_types=None, mark_set=None, connected_through_dashboard=False):
super().__init__(queues, websocket, input_types, mark_set, connected_through_dashboard)
def __init__(self, queues, websocket=None, input_types=None, mark_set=None, turn_based_conversation=False):
super().__init__(queues, websocket, input_types, mark_set, turn_based_conversation)
self.io_provider = 'plivo'

async def call_start(self, packet):
Expand Down
4 changes: 2 additions & 2 deletions bolna/input_handlers/telephony_providers/twilio.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@


class TwilioInputHandler(TelephonyInputHandler):
def __init__(self, queues, websocket=None, input_types=None, mark_set=None, connected_through_dashboard=False):
super().__init__(queues, websocket, input_types, mark_set, connected_through_dashboard)
def __init__(self, queues, websocket=None, input_types=None, mark_set=None, turn_based_conversation=False):
super().__init__(queues, websocket, input_types, mark_set, turn_based_conversation)
self.io_provider = 'twilio'

async def call_start(self, packet):
Expand Down
54 changes: 27 additions & 27 deletions bolna/llms/openai_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,6 @@

logger = configure_logger(__name__)
load_dotenv()

async def trigger_api(url, method, param, api_token, **kwargs):
try:
code = compile(param % kwargs, "<string>", "exec")
exec(code, globals(), kwargs)
req = param % kwargs
logger.info(f"Params {param % kwargs} \n {type(req)} \n {param} \n {kwargs} \n\n {req}")

headers = {'Content-Type': 'application/json'}
if api_token:
headers = {'Content-Type': 'application/json', 'Authorization': api_token}
if method == "get":
logger.info(f"Sending request {req}, {url}, {headers}")
response = requests.get(url, params=json.loads(req), headers=headers)
logger.info(f"Response from The servers {response.text}")
return response.text
elif method == "post":
logger.info(f"Sending request {json.loads(req)}, {url}, {headers}")
response = requests.post(url, json=json.loads(req), headers=headers)
logger.info(f"Response from The server {response.text}")
return response.text
except Exception as e:
message = str(f"We send {method} request to {url} & it returned us this error:", e)
logger.error(message)
return message


class OpenAiLLM(BaseLLM):
Expand Down Expand Up @@ -72,7 +47,32 @@ def __init__(self, max_tokens=100, buffer_size=40, model="gpt-3.5-turbo-16k", te
self.async_client = AsyncOpenAI(api_key=llm_key)
self.run_id = kwargs.get("run_id", None)
self.gave_out_prefunction_call_message = False


async def trigger_api(self, url, method, param, api_token, **kwargs):
try:
code = compile(param % kwargs, "<string>", "exec")
exec(code, globals(), kwargs)
req = param % kwargs
logger.info(f"Params {param % kwargs} \n {type(req)} \n {param} \n {kwargs} \n\n {req}")

headers = {'Content-Type': 'application/json'}
if api_token:
headers = {'Content-Type': 'application/json', 'Authorization': api_token}
if method == "get":
logger.info(f"Sending request {req}, {url}, {headers}")
response = requests.get(url, params=json.loads(req), headers=headers)
logger.info(f"Response from The servers {response.text}")
return response.text
elif method == "post":
logger.info(f"Sending request {json.loads(req)}, {url}, {headers}")
response = requests.post(url, json=json.loads(req), headers=headers)
logger.info(f"Response from The server {response.text}")
return response.text
except Exception as e:
message = str(f"We send {method} request to {url} & it returned us this error:", e)
logger.error(message)
return message

async def generate_stream(self, messages, synthesize=True, request_json=False, meta_info = None):
if len(messages) == 0:
raise Exception("No messages provided")
Expand Down Expand Up @@ -140,7 +140,7 @@ async def generate_stream(self, messages, synthesize=True, request_json=False, m
method = func_dict['method']
param = func_dict['param']
api_token = func_dict['api_token']
response = await trigger_api(url= url, method=method.lower(), param= param, api_token= api_token, **resp)
response = await self.trigger_api(url= url, method=method.lower(), param= param, api_token= api_token, **resp)
content = f"We did made a function calling for user. We hit the function : {called_fun}, we hit the url {url} and send a {method} request and it returned us the response as given below: {str(response)} \n\n . Kindly understand the above response and convey this response in a conextual to user."
model_args["messages"].append({"role":"system","content":content})
logger.info(f"Logging function call parameters ")
Expand Down
13 changes: 10 additions & 3 deletions bolna/memory/cache/vector_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
from typing import List
import numpy as np
from fastembed import TextEmbedding
from sentence_transformers import util
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

logger = configure_logger(__name__)

Expand All @@ -20,9 +23,13 @@ def set(self, documents):
)

def __get_top_cosine_similarity_doc(self, query_embedding):
scores = np.dot(self.embeddings, query_embedding)
sorted_scores = np.argsort(scores)[::-1]
return self.documents[sorted_scores[0]]
#util.pytorch_cos_sim(self.embeddings, query_embedding)
# scores = np.dot(self.embeddings, query_embedding)
# sorted_scores = np.argsort(scores)[::-1]

similarities = cosine_similarity([query_embedding], self.embeddings)[0]
most_similar_index = np.argmax(similarities)
return self.documents[most_similar_index]

def get(self, query):
if self.index_provider is None:
Expand Down

0 comments on commit e5da076

Please sign in to comment.