You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
668 lines
38 KiB
668 lines
38 KiB
# -*- coding: utf-8 -*-
|
|
#############################################################################
|
|
#
|
|
# Cybrosys Technologies Pvt. Ltd.
|
|
#
|
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>)
|
|
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>)
|
|
#
|
|
# You can modify it under the terms of the GNU AFFERO
|
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details.
|
|
#
|
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
|
# (AGPL v3) along with this program.
|
|
# If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
#############################################################################
|
|
from googletrans import Translator
|
|
from odoo import api, fields, models
|
|
import requests
|
|
from odoo.exceptions import AccessError, ValidationError
|
|
|
|
PROVIDER = [
|
|
('openai', 'openai'),
|
|
('together-ai', 'Together-AI'),
|
|
('anyscale', 'AnyScale'),
|
|
('openrouter', 'OpenRouter'),
|
|
('perplexity-ai', 'Perplexity-AI'),
|
|
('deepinfra', 'DeepInfra'),
|
|
('groq', 'Groq'),
|
|
('anthropic', 'Anthropic')
|
|
]
|
|
TRANSCRIBER_PROVIDER = [('deepgram', 'Deepgram'),
|
|
('talkscriber', 'Talkscriber'),
|
|
('gladia', 'Gladiya')]
|
|
STATE = [('draft', 'Draft'),
|
|
('done', 'Done')]
|
|
|
|
|
|
class OraAi(models.Model):
|
|
"""Model for the order assistant."""
|
|
_name = "ora.ai"
|
|
_inherit = "mail.thread"
|
|
_description = "Order Assistant"
|
|
|
|
name = fields.Char(string="Name", required=True,
|
|
help="Name of the AI assistant configuration.")
|
|
id_assistant = fields.Char(string="Assistant id", readonly=True,
|
|
copy=False, help="Unique identifier of the"
|
|
"assistant created via "
|
|
"external API.")
|
|
first_message = fields.Text(string="First Message",
|
|
compute="_compute_first_message",
|
|
help="The first message that the assistant"
|
|
" will say.", )
|
|
provider = fields.Selection(string="Provider", selection=PROVIDER,
|
|
default="openai", required=True,
|
|
help="Select the LLM provider for generating"
|
|
" responses (e.g., OpenAI, Anthropic, "
|
|
"Groq).")
|
|
provider_model_id = fields.Many2one('provider.model',
|
|
string="Model",
|
|
domain="[('id', 'in',provider_model_ids)]",
|
|
required=True,
|
|
help="Select the specific AI model"
|
|
"offered by the chosen provider.")
|
|
provider_model_ids = fields.Many2many('provider.model',
|
|
compute='_compute_provider_models',
|
|
help="Filtered list of models "
|
|
"available based on the "
|
|
"selected provider.")
|
|
transcriber_provider = fields.Selection(selection=TRANSCRIBER_PROVIDER,
|
|
string="Transcriber Provider",
|
|
default='deepgram', required=True,
|
|
help="Speech-to-text service "
|
|
"provider for transcribing "
|
|
"voice input.")
|
|
transcriber_model_id = fields.Many2one('transcriber.model',
|
|
string="Transcriber Model",
|
|
required=True,
|
|
domain="[('id', 'in', transcriber_model_ids)]",
|
|
help="Choose the transcription "
|
|
"model best suited for the "
|
|
"conversation context.")
|
|
transcriber_model_ids = fields.Many2many('transcriber.model',
|
|
compute='_compute_transcriber_models',
|
|
help="List of available "
|
|
"transcription models "
|
|
"filtered by selected "
|
|
"provider.")
|
|
language_id = fields.Many2one('ora.language',
|
|
string="Language",
|
|
help="Select the default language "
|
|
"used by the assistant.")
|
|
is_lang_switch = fields.Boolean(string="Multi-Language",
|
|
help="Enable support for multiple "
|
|
"languages. Assistant will switch"
|
|
" languages if needed.")
|
|
language_ids = fields.Many2many('ora.language',
|
|
string="Languages",
|
|
help="List of available languages for "
|
|
"dynamic language switching during "
|
|
"the session.")
|
|
state = fields.Selection(selection=STATE, tracking=True,
|
|
default="draft", copy=False,
|
|
help="Current status of the assistant"
|
|
" configuration")
|
|
date = fields.Date(string="Date",
|
|
help="Date when the assistant was created or"
|
|
" activated.")
|
|
prompt = fields.Text(string="Contents",
|
|
compute="_compute_prompt",
|
|
readonly=True,
|
|
help="The Contents can be used to configure the"
|
|
" context, role, personality, instructions "
|
|
"and so on for the assistant.", )
|
|
end_call_phrases = fields.Text(string="End Call Phrases",
|
|
default="goodbye",
|
|
help="Enter phrases, separated by commas, "
|
|
"that will trigger the Assistant to "
|
|
"end the call when spoken.")
|
|
file_ids = fields.Many2many('ora.file',
|
|
string="Knowledge Base",
|
|
help="Knowledge Base is a collection of"
|
|
" custom documents that contain "
|
|
"information on specific topics or"
|
|
" domains.")
|
|
function_description = fields.Char(string="Function",
|
|
compute="_compute_function_description",
|
|
help="Generated JSON-based function")
|
|
language_function_description = fields.Char(
|
|
string="language function description",
|
|
compute="_compute_language_function_description",
|
|
help="Generated function spec for language preference "
|
|
"handling used by the assistant.")
|
|
|
|
def _translate_text(self, text, target_lang):
|
|
"""Translates the given text into the specified language using
|
|
Googletrans."""
|
|
translator = Translator()
|
|
translated = translator.translate(text, dest=target_lang)
|
|
return translated.text
|
|
|
|
@api.depends('provider')
|
|
def _compute_provider_models(self):
|
|
"""Populate the list of available provider models based on the
|
|
selected provider."""
|
|
for rec in self:
|
|
provider_model = rec.provider_model_id.search([])
|
|
for provider in PROVIDER:
|
|
if rec.provider == provider[0]:
|
|
filtered_model = provider_model.filtered(
|
|
lambda l: l.provider == provider[0])
|
|
rec.provider_model_ids = [fields.Command.link(res.id)
|
|
for res in filtered_model]
|
|
if not rec.provider:
|
|
rec.provider_model_ids = [fields.Command.link(res.id)
|
|
for res in provider_model]
|
|
|
|
@api.depends('transcriber_provider')
|
|
def _compute_transcriber_models(self):
|
|
"""Assign available transcriber models based on the selected
|
|
transcriber provider"""
|
|
for rec in self:
|
|
provider_model = rec.transcriber_model_id.search([])
|
|
for provider in TRANSCRIBER_PROVIDER:
|
|
if rec.transcriber_provider == provider[0]:
|
|
filtered_model = provider_model.filtered(
|
|
lambda l: l.provider == provider[0])
|
|
rec.transcriber_model_ids = [fields.Command.link(res.id)
|
|
for res in filtered_model]
|
|
if not rec.transcriber_provider:
|
|
rec.transcriber_model_ids = [fields.Command.link(res.id)
|
|
for res in provider_model]
|
|
|
|
@api.depends('language_ids')
|
|
def _compute_language_function_description(self):
|
|
"""Computes a formatted description of available languages
|
|
for the assistant."""
|
|
for rec in self:
|
|
language_function_description = ""
|
|
if rec.language_ids and rec.is_lang_switch:
|
|
for lang in rec.language_ids:
|
|
language_function_description += (f"{lang.name} : "
|
|
f"{lang.code}, \n")
|
|
rec.language_function_description = (
|
|
language_function_description)
|
|
else:
|
|
rec.language_function_description = ""
|
|
|
|
def _compute_prompt(self):
|
|
"""Computes a detailed prompt for the voice assistant based on
|
|
the current product catalog and availability."""
|
|
prompt = (
|
|
f"You are the voice assistant of the 'My Company (San Francisco)' "
|
|
f"Restaurant, responsible for taking customer orders. "
|
|
f"Your primary task is to carefully listen to and process the "
|
|
f"customer's order details.\n"
|
|
f"Must ask for Customer Name.\n"
|
|
f"If a customer asks for product details, first explain all "
|
|
f"the details by category. Then, mention the product name, "
|
|
f"followed by its price, in that order.\n")
|
|
out_of_stock_prompt = "."
|
|
out_of_stock_prompt_2 = "."
|
|
cate_ids = self.env['product.public.category'].search([])
|
|
for rec in cate_ids:
|
|
prompt += f"\n{rec.display_name}\n"
|
|
products = self.env['product.template'].search(
|
|
[('public_categ_ids', 'in', rec.id),
|
|
('is_published', '=', True)])
|
|
for record in products:
|
|
prompt += f" • {record.name}, ${record.list_price}\n"
|
|
optional_products = record.optional_product_ids.mapped('name')
|
|
variants = record.product_variant_ids
|
|
if (record.detailed_type == 'product' and
|
|
not record.allow_out_of_stock_order):
|
|
prompt += (f" -quantity available:"
|
|
f"{int(record.qty_available)}\n")
|
|
out_of_stock_prompt = (", don't say the available quantity"
|
|
" even if it is out of stock, say "
|
|
"only when it asked.")
|
|
out_of_stock_prompt_2 = (" ,if the quantity is greater than"
|
|
" available quantity just say the"
|
|
" available quantity in the stock."
|
|
" ")
|
|
if len(variants) > 1:
|
|
prompt += f" - {len(variants)} variants\n"
|
|
for variant in variants:
|
|
attribute_values = (
|
|
variant.product_template_variant_value_ids.mapped(
|
|
'name'))
|
|
variant_details = ", ".join(attribute_values)
|
|
prompt += (f" * {variant_details}, "
|
|
f"${variant.lst_price}\n")
|
|
if len(optional_products) > 0:
|
|
prompt += (f" - {len(optional_products)} "
|
|
f"Optional Products\n")
|
|
for opt_product in optional_products:
|
|
prompt += f" • {opt_product} \n"
|
|
none_categ_products = self.env['product.template'].search(
|
|
[("public_categ_ids", "=", False), ('is_published', '=', True)])
|
|
prompt += f"\n None category \n"
|
|
for rec in none_categ_products:
|
|
prompt += f" • {rec.name}, ${rec.list_price} \n"
|
|
prompt += (
|
|
f"\n When a customer interacts with you through a voice command"
|
|
f" to place an order, \n you should proceed by listing out all "
|
|
f"product options available without excluding any mentioned"
|
|
f" products {out_of_stock_prompt}\n"
|
|
f"if the product has variants ask for which variant do you need.\n"
|
|
f"After customer select a product the ask for How many Quantity"
|
|
f" do you need{out_of_stock_prompt_2}\n"
|
|
f"if the product has optional products ask do you want to add ? \n"
|
|
f"Before confirming the order, ask Would you like to add anything"
|
|
f" else to your order before confirming? \n"
|
|
f"Once the customer finalizes their order and informs you of their"
|
|
f" selection, ask, 'May I confirm the order?' If they respond "
|
|
f"affirmatively, kindly repeat back the order details "
|
|
f"for confirmation.\n Keep all your responses short and simple."
|
|
f" Use casual language, phrases like Umm..., Well..., and I "
|
|
f"mean are preferred.\n If customer order confirmed. say thankyou"
|
|
f" for ordering and goodbye.\n This is a voice conversation, "
|
|
f"so keep your responses short, like in a real conversation. "
|
|
f"Don't ramble for too long")
|
|
self.prompt = prompt
|
|
|
|
def _compute_function_description(self):
|
|
"""Computes a descriptive mapping of product names,
|
|
variants, and their IDs."""
|
|
function_description = ""
|
|
products = self.env['product.product'].search([])
|
|
for product in products:
|
|
variant_values = ", ".join(
|
|
product.product_template_attribute_value_ids.mapped('name'))
|
|
function_description += (f"{product.name} {variant_values} :"
|
|
f" {product.id}, \n")
|
|
self.function_description = function_description
|
|
|
|
def _compute_first_message(self):
|
|
"""Computes the initial greeting message for the voice assistant."""
|
|
for rec in self:
|
|
languages = rec.language_ids.mapped('name')
|
|
result = ', '.join(languages)
|
|
if rec.language_ids and rec.is_lang_switch:
|
|
rec.first_message = (f"Hey iam your {self.name}, Which "
|
|
f"language would you like to choose?"
|
|
f" We have several languages available"
|
|
f" such as {result}. Which Language "
|
|
f"would you prefer?")
|
|
else:
|
|
rec.first_message = (f"Hey iam {self.name}, How can i "
|
|
f"help you today?")
|
|
|
|
def action_create_assistant(self):
|
|
"""Creates a voice assistant instance using the Vapi.ai API based
|
|
on the assistant configuration."""
|
|
base_url = self.env['ir.config_parameter'].sudo().get_param(
|
|
'web.base.url')
|
|
bearer = self.env['ir.config_parameter'].sudo().get_param(
|
|
'ora_ai_base.vapi_private_api_key')
|
|
protocol = "https" if base_url.startswith("https") else "http"
|
|
if protocol == 'http':
|
|
raise AccessError("URL Must be HTTPS")
|
|
if protocol == 'https':
|
|
url = "https://api.vapi.ai/assistant"
|
|
if self.is_lang_switch and self.language_ids:
|
|
language = "en"
|
|
voice = "qgj3VahzWaAK300v6H27"
|
|
first_message = self.first_message
|
|
else:
|
|
language = self.language_id.code
|
|
voice = self.language_id.voice
|
|
first_message = self._translate_text(self.first_message,
|
|
language)
|
|
payload = {
|
|
"transcriber": {
|
|
"provider": self.transcriber_provider,
|
|
"model": self.transcriber_model_id.key,
|
|
"language": language,
|
|
"smartFormat": True
|
|
},
|
|
"voice": {
|
|
"voiceId": voice,
|
|
"provider": "11labs",
|
|
},
|
|
"model": {
|
|
"provider": self.provider,
|
|
"model": self.provider_model_id.key,
|
|
"knowledgeBase": {
|
|
"provider": "canonical",
|
|
"fileIds": self.file_ids.mapped('id_file')
|
|
},
|
|
"messages": [
|
|
{
|
|
"role": "system",
|
|
"content": self.prompt
|
|
}
|
|
],
|
|
"tools": [
|
|
{
|
|
"type": "function",
|
|
"async": True,
|
|
"function": {
|
|
"name": "GetFinalOrderDetails",
|
|
"description": f"This function is designed to"
|
|
f"retrieve order details from "
|
|
f"a voice assistant, with order"
|
|
f" confirmation being a "
|
|
f"prerequisite for providing "
|
|
f"the details. The function "
|
|
f"will be triggered after "
|
|
f"confirming the order. "
|
|
f"Upon asking a confirmation "
|
|
f"question and receiving a "
|
|
f"'yes' response, this function"
|
|
f" must be activated.",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"OrderDetails": {
|
|
"type": "object",
|
|
"properties": {
|
|
"Products": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {
|
|
"Quantity": {
|
|
"type": "number",
|
|
'description': f"This parameter is used to retrieve the product quantity. {{ quantities: customer ordered quantity }}"
|
|
},
|
|
"Customer": {
|
|
"type": "string",
|
|
'description': f"This parameter is used to retrieve the customer name. {{ customer: customer name }}"
|
|
},
|
|
"Product": {
|
|
"type": "string",
|
|
'description': f"This parameter is used to retrieve the Product name. {{ product: product name }}",
|
|
},
|
|
"Variant": {
|
|
"type": "string",
|
|
"description": f"This parameter is used to retrieve the Product variant details. For example, if a customer order is confirmed, then retrieve {{ Variant: product Variant }}",
|
|
},
|
|
"productId": {
|
|
"type": "number",
|
|
"description": f'Determine the Product ID of the product in the confirmed order. Fetch the correct ID based on the following product and ID mapping: , {self.function_description}',
|
|
}
|
|
},
|
|
"required": [
|
|
"Quantity",
|
|
"Customer",
|
|
"Product",
|
|
"Variant",
|
|
"productId"]
|
|
}
|
|
}
|
|
},
|
|
"required": ["products"]
|
|
}
|
|
},
|
|
"required": ["OrderDetails"]
|
|
}
|
|
},
|
|
"server": {
|
|
"url": f"{base_url}/vapi_voice_assistant/details",
|
|
}
|
|
},
|
|
{
|
|
"type": "function",
|
|
"async": True,
|
|
"function": {
|
|
"name": "SetUserLanguagePreference",
|
|
"description": "This custom function retrieves the selected languages at the beginning of a voice assistance session if the user chooses the languages at the start of the session. This function must achieve the LanguageCode",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"LanguagePreference": {
|
|
"type": "object",
|
|
"properties": {
|
|
"LanguageCode": {
|
|
"type": "string",
|
|
"description": f"The code of the language chosen by the user. For example: en-IN for English (India), en-US for English (United States), es-LA for Spanish (Latin America).,our lanagues and its code is {self.language_function_description}"
|
|
},
|
|
},
|
|
"required": [
|
|
"LanguageCode",
|
|
]
|
|
}
|
|
},
|
|
"required": ["LanguagePreference"]
|
|
}
|
|
},
|
|
"server": {
|
|
"url": f"{base_url}/vapi_voice_assistant/language_details"
|
|
}
|
|
},
|
|
],
|
|
},
|
|
"clientMessages": ["conversation-update", "function-call",
|
|
"hang", "model-output", "speech-update",
|
|
"status-update", "transcript", "tool-calls",
|
|
"user-interrupted", "voice-input"],
|
|
"serverMessages": ["conversation-update", "end-of-call-report",
|
|
"function-call", "hang", "speech-update",
|
|
"status-update", "tool-calls",
|
|
"transfer-destination-request",
|
|
"user-interrupted"],
|
|
"messagePlan": {
|
|
"idleMessages": ["Feel free to ask whenever you're ready.",
|
|
"I'm still here if you need assistance.",
|
|
"How can I assist you further?",
|
|
"Are you still there?",
|
|
"Looking for something specific? I can "
|
|
"assist with that!",
|
|
"Need help choosing a product? I'm here "
|
|
"to assist.",
|
|
"I'm here if you need any assistance with"
|
|
"your shopping."],
|
|
"idleTimeoutSeconds": 5,
|
|
"idleMessageMaxSpokenCount": 10},
|
|
"name": self.name,
|
|
"firstMessage": first_message,
|
|
"serverUrl": f"{base_url}/vapi_voice_assistant/status",
|
|
"endCallPhrases": [
|
|
self.end_call_phrases
|
|
],
|
|
}
|
|
headers = {
|
|
"Authorization": f"Bearer {bearer}",
|
|
"Content-Type": "application/json"
|
|
}
|
|
response = requests.request("POST", url, json=payload,
|
|
headers=headers)
|
|
json_response = response.json()
|
|
self.write({'date': json_response['createdAt'],
|
|
'state': 'done',
|
|
'id_assistant': json_response['id']
|
|
})
|
|
|
|
def write(self, vals):
|
|
"""Update the external Vapi.ai assistant configuration when
|
|
the record is modified."""
|
|
res = super().write(vals)
|
|
if self.id_assistant:
|
|
base_url = self.env['ir.config_parameter'].sudo().get_param(
|
|
'web.base.url')
|
|
bearer = self.env['ir.config_parameter'].sudo().get_param(
|
|
'ora_ai_base.vapi_private_api_key')
|
|
url = f"https://api.vapi.ai/assistant/{self.id_assistant}"
|
|
if self.is_lang_switch and self.language_ids:
|
|
language = "en"
|
|
voice = "qgj3VahzWaAK300v6H27"
|
|
first_message = self.first_message
|
|
end_message = self.end_call_phrases
|
|
else:
|
|
language = self.language_id.code
|
|
voice = self.language_id.voice
|
|
first_message = self._translate_text(self.first_message,
|
|
language)
|
|
end_message = self._translate_text(self.end_call_phrases,
|
|
language)
|
|
payload = {
|
|
"transcriber": {
|
|
"provider": self.transcriber_provider,
|
|
"model": self.transcriber_model_id.key,
|
|
"language": language,
|
|
"smartFormat": True},
|
|
"voice": {
|
|
"voiceId": voice,
|
|
"provider": "11labs"},
|
|
"model": {
|
|
"provider": self.provider,
|
|
"model": self.provider_model_id.key,
|
|
"knowledgeBase": {
|
|
"provider": "canonical",
|
|
"fileIds": self.file_ids.mapped('id_file')},
|
|
"messages": [{
|
|
"role": "system",
|
|
"content": self.prompt}],
|
|
"tools": [{
|
|
"type": "function",
|
|
"async": True,
|
|
"function": {
|
|
"name": "GetFinalOrderDetails",
|
|
"description": f"This function is designed to"
|
|
f" retrieve order details from"
|
|
f" a voice assistant, with "
|
|
f"order confirmation being a "
|
|
f"prerequisite for providing "
|
|
f"the details. The function "
|
|
f"will be triggered after "
|
|
f"confirming the order. Upon"
|
|
f" asking a confirmation "
|
|
f"question and receiving a "
|
|
f"'yes' response, this function"
|
|
f" must be activated.",
|
|
"parameters": {"type": "object", "properties": {
|
|
"OrderDetails":
|
|
{"type": "object", "properties":
|
|
{"Products": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {
|
|
"Quantity": {
|
|
"type": "number",
|
|
'description': f"This parameter is used to retrieve the product quantity. {{ quantities: customer ordered quantity }}"},
|
|
"Customer": {
|
|
"type": "string",
|
|
'description': f"This parameter is used to retrieve the customer name. {{ customer: customer name }}"},
|
|
"Product": {
|
|
"type": "string",
|
|
'description': f"This parameter is used to retrieve the Product name. {{ product: product name }}"},
|
|
"Variant": {
|
|
"type": "string",
|
|
"description": f"This parameter is used to retrieve the Product variant details. For example, if a customer order is confirmed, then retrieve {{ Variant: product Variant }}"},
|
|
"productId": {
|
|
"type": "number",
|
|
"description": f'Determine the Product ID of the product in the confirmed order. Fetch the correct ID based on the following product and ID mapping: , {self.function_description}'}},
|
|
"required": [
|
|
"Quantity",
|
|
"Customer",
|
|
"Product",
|
|
"Variant",
|
|
"productId"]}}},
|
|
"required": ["products"]}},
|
|
"required": ["OrderDetails"]}},
|
|
"server": {
|
|
"url": f"{base_url}/vapi_voice_assistant/details"}},
|
|
{"type": "function",
|
|
"async": True,
|
|
"function": {
|
|
"name": "SetUserLanguagePreference",
|
|
"description": "This custom function retrieves"
|
|
"the selected languages at the "
|
|
"beginning of a voice "
|
|
"assistance session if the user"
|
|
" chooses the languages at the "
|
|
"start of the session. This "
|
|
"function must achieve the "
|
|
"LanguageCode",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"LanguagePreference": {
|
|
"type": "object",
|
|
"properties": {
|
|
"LanguageCode": {
|
|
"type": "string",
|
|
"description": f"The code of the language chosen by the user. For example: en-IN for English (India), en-US for English (United States), es-LA for Spanish (Latin America).,our lanagues and its code is {self.language_function_description}"}, },
|
|
"required": [
|
|
"LanguageCode", ]}},
|
|
"required": ["LanguagePreference"]}},
|
|
"server": {
|
|
"url": f"{base_url}/vapi_voice_assistant/language_details"}}, ], },
|
|
"clientMessages": ["conversation-update", "function-call",
|
|
"hang", "model-output", "speech-update",
|
|
"status-update", "transcript", "tool-calls",
|
|
"user-interrupted", "voice-input"],
|
|
"serverMessages": ["conversation-update", "end-of-call-report",
|
|
"function-call", "hang", "speech-update",
|
|
"status-update", "tool-calls",
|
|
"transfer-destination-request",
|
|
"user-interrupted"],
|
|
"messagePlan": {
|
|
"idleMessages": ["Feel free to ask whenever you're ready.",
|
|
"I'm still here if you need assistance.",
|
|
"How can I assist you further?",
|
|
"Are you still there?",
|
|
"Looking for something specific? I can "
|
|
"assist with that!",
|
|
"Need help choosing a product? I'm here"
|
|
" to assist.",
|
|
"I'm here if you need any assistance with"
|
|
" your shopping."],
|
|
"idleTimeoutSeconds": 5,
|
|
"idleMessageMaxSpokenCount": 10},
|
|
"name": self.name,
|
|
"firstMessage": first_message,
|
|
"serverUrl": f"{base_url}/vapi_voice_assistant/status",
|
|
"endCallPhrases": [
|
|
end_message], }
|
|
headers = {
|
|
"Authorization": f"Bearer {bearer}",
|
|
"Content-Type": "application/json"}
|
|
response = requests.request("PATCH", url, json=payload,
|
|
headers=headers)
|
|
if response.status_code != 200:
|
|
raise ValidationError(
|
|
"Assistant Update Failed: %s" % response.text)
|
|
return res
|
|
|
|
def unlink(self):
|
|
"""Ensure the corresponding assistant in the Vapi.ai platform is
|
|
also deleted when the Odoo record is removed."""
|
|
bearer = self.env['ir.config_parameter'].sudo().get_param(
|
|
'ora_ai_base.vapi_private_api_key')
|
|
for rec in self:
|
|
url = f"https://api.vapi.ai/assistant/{rec.id_assistant}"
|
|
headers = {
|
|
"Authorization": f"Bearer {bearer}"}
|
|
requests.request("DELETE", url, headers=headers)
|
|
return super().unlink()
|
|
|
|
def action_assistant_testing(self):
|
|
"""Triggers the client-side action to initiate voice assistant
|
|
testing."""
|
|
bearer = self.env['ir.config_parameter'].sudo().get_param(
|
|
'ora_ai_base.vapi_public_api_key')
|
|
return {
|
|
'type': 'ir.actions.client',
|
|
'tag': 'action_voice_assistant',
|
|
'params': {
|
|
'assistant_id': self.id_assistant,
|
|
'api_key': bearer,
|
|
'assistant_name': self.name}}
|
|
|
|
@api.model
|
|
def reset_assistant(self, assistant_id):
|
|
""" Reset Assistant with old values.
|
|
After Assistant updated through js in the VAPI end."""
|
|
if assistant_id:
|
|
rec = self.search([('id_assistant', '=', assistant_id)])
|
|
rec.write({})
|
|
|