Zum Hauptinhalt springen

Voice Agents JS Example (OpenAI Call Screening)

Voice Agents JS Example - OpenAI Call Screening

This example demonstrates how OpenAI can act as an agent for screening calls. The AI first answers as a concierge/receptionist, and collects some basic information (by asking some pre-defined questions). Then it can put the call on hold to connect the right agent. It gives the basic information it has collected and asks if they want to accept the call. Of course, it's a very simple example with simple prompts, but in real use the prompts can be made elaborate to cater for different scenarios and avoid repetitions etc.

OpenAI Setup:

Please refer to the documentation here for OpenAI setup, including configuring project, the webhook, API key and then setting up the trunk and dialplan on Vodia.

How call screening works:

This example uses a dual-mode approach where the same Voice Agents script behaves differently based on the ivraction parameter:

  1. Concierge Mode (initial call):

    • Caller speaks to AI
    • AI asks some questions to collect some information
    • Passes the information to the agent
  2. Attendant Mode (destination call):

    • The destination agent receives a call with ivraction: ( Information collected above and passed here )
    • The information collected is presented to the agent and asked if they want to accept the call
    • Based on the response, AI calls call_accept() or call_reject()
note

The model specified in the script is gpt-realtime. You must verify its current availability under your OpenAI plan. Additionally, check for potential rate limit/cost impacts and identify a suitable alternative model should the version be inaccessible

// OpenAI integration through SIP
// Auto-generated from AI Assistant configuration
'use strict'

var secret = "sk-proj-KEY"

var timer = setTimeout(function() {
call.transfer('700')
}, 300000)

call.http(onhttp)
function onhttp (args) {
console.log('Open AI Ringing ... ')
console.log(JSON.stringify(args))
const body = JSON.parse(args.body)
if (body.type == 'realtime.call.incoming') {
const callid = body.data.call_id
system.http({
method: 'POST',
url: 'https://api.openai.com/v1/realtime/calls/' + callid + '/accept',
header: [
{ name: 'Authorization', value: 'Bearer ' + secret, secret: true },
{ name: 'Content-Type', value: 'application/json' }
],
body: JSON.stringify({
type: "realtime",
model: "gpt-realtime",
instructions: "You are a helpful assistant."
}),
callback: function(code, response, headers) {
connected(code, response, headers, callid)
}
})
}
else if (body.type == 'att_transfer') {
if (body.result == 'true') {
console.log('Call accepted')
call.say({ text: 'Call accepted', callback: function() { call.hangup() }})
}
else {
console.log('Call rejected')
console.log('Resume call')
call.transfer({ action: 'resume' })
call.say({ text: 'Agents busy at this time. Please call later.', callback: function() {
call.hangup()
// Or maybe give new instructions to AI for the call. Or transfer it to a queue or another Voice Agents.
}
})
}
}
}

function connected(code, response, headers, callid) {
var ws = new Websocket("wss://api.openai.com/v1/realtime?call_id=" + callid)
ws.header([
{ name: "Authorization", value: "Bearer " + secret, secret: true },
{ name: "User-Agent", value: "Vodia-PBX/70" }
])

ws.on('open', function() {
console.log("Websocket opened")
var instructions = ""
console.log('Voice Agents action:')
console.log(call.ivraction)
if (call.ivraction) {
instructions = "A question has been asked they want to accept the call. Please wait for a response: If they explicitly accept the call (like ok I will take it or yes or connect me etc.), then call the function call_accept. If they explicitly reject the call (like no, or don't connect me, or I can't take it right now etc.) then call the function call_reject. Do not call anything otherwise. Do not respond with normal text for these intents."
}
else {
instructions = "Collect the information, like name, company, what the problem is. Once done, then call the function 'transfer_call', with the 'info' as the argument that holds the collected information as a string. Do not give out any other information except to collect the above information. Do not respond with normal text for these intents."
}
const update = {
"type": "session.update",
"session": {
"type": "realtime",
"instructions": instructions,
"tools": [
{
"type": "function",
"name": "transfer_call",
"description": "Hold the call, and start a new call with the info collected.",
"parameters": {
"type": "object",
"properties": {
"info": {
"type": "string",
"description": "Information collected"
}
},
"required": ["info"]
}
},
{
"type": "function",
"name": "call_accept",
"description": "Accepts the call for attended transfer"
},
{
"type": "function",
"name": "call_reject",
"description": "Rejects the call for attended transfer"
}
],
"tool_choice": "auto"
}
}
ws.send(JSON.stringify(update))

var greeting = ""
if (call.ivraction) {
console.log('Original from:')
console.log(call.orig_from)
const origname = call.orig_from.split('"')[1]
greeting = {
"type": "response.create",
"response": {
"instructions": "Greet with: " + "There is a support call from:" + call.ivraction + "Would you like to accept the call?"
}
}
}
else {
greeting = {
"type": "response.create",
"response": {
"instructions": "Greet with: " + "What would you like to do?"
}
}
}
ws.send(JSON.stringify(greeting))
})
ws.on('close', function() { console.log("Websocket closed") })

ws.on('message', function(message) {
//call.log('Websocket message:')
//call.log(message)
const evt = JSON.parse(message)
if (
evt.type === "response.output_item.done" &&
evt.item.type === "function_call"
) {
const args = JSON.parse(evt.item.arguments)
const info = args.info
console.log('Information collected:')
if (typeof info == 'string') {
console.log(info)
}
if (evt.item.name === "transfer_call") {
console.log('Hold call')
call.transfer({ action: 'hold' })
call.dial( { account: '88', dest:'403', from: '', cobj: call.callid, ivraction: info } )
}
else if (evt.item.name === "call_accept") {
console.log('Accept the attended transfer call')
call.transfer({ action: 'accept'} )
}
else if (evt.item.name === "call_reject") {
console.log('Reject the attended transfer call')
call.transfer({ action: 'reject'} )
}
}
})

function say (msg) {
const message = {
"type": "response.create",
"response": {
"instructions": "Say: " + msg
}
}
ws.send(JSON.stringify(message))
}

ws.connect()
}

call.dial('openai')

Call Flow Diagram:

[Caller] → [AI Concierge] "AI asks some questions (like name, company, problem description etc.). Collects information."

AI puts the call on hold

AI calls the agent and passes on the information collected

AI asks the agent whether to connect the call?

[Agent responds]

"Yes" → call_accept() → [Calls Connected]
"No" → call_reject() → [Call is reumed to play a message like agent is busy, then the call can be terminated. Or transferred somewhere else etc.]

For more information on Vodia's JavaScript capabilities, refer to: Vodia Backend JavaScript Documentation