AVR-Agent Integration (OpenAI Realtime and others)

We worked with the developer of avr-agent (Agent Voice Response · GitHub) to get this working with VitalPBX. Gcareri has been awesome to work with and is building a nice community around AVR.

We’ve added a custom dialplan to route extensions 5001-5009 to avr-agent and gcareri modified avr-agent so we can pass information to it to load a prompt.

First you’ll need to create an AMI user however I haven’t narrowed down permissions but assigning all permissions available in the VitalPBX GUI doesn’t seem to be enough. I created the user in the GUI then edited the file in /etc/asterisk/vitalpbx/manager__50-1-users.conf and set read=all and write=all. I don’t know if this file is only overwritten by VitalPBX when you edit the AMI users. Editing call flows and then reloading didn’t affect it. Make sure to lock this down to the IP address avr-agent connects from.

Next add a custom dialplan option. In
/etc/asterisk/vitalpbx/extensions__90-avr.conf add

[cos-all-custom](+)
exten => _500[1-9],1,Answer()
 same => n,Ringing()
 same => n,Set(AVR_TENANT_ID=T${IF($["${TENANT_ID}"=""]?1:${TENANT_ID})}_${CALL_DESTINATION})
 same => n,NoOp(AVR_TENANT_ID is ${AVR_TENANT_ID})
 same => n,Set(UUID=${SHELL(uuidgen | tr -d '\n')})
 same => n,Dial(AudioSocket/127.0.0.1:5001/${UUID})
 same => n,Hangup()

Here’s our docker-compose.yml

Edited to stick with avr-stst-openai version 1.3.4 - I’ll update as this changes 9/5/25
Edited to move to latest avr-sts-openai and add avr-instructions container 9/8/25

services:
  avr-core:
    image: agentvoiceresponse/avr-core
    platform: linux/x86_64
    container_name: avr-core
    restart: always
    environment:
      - PORT=5001 
      - STS_URL=ws://avr-sts-openai:6030
    ports:
      - 5001:5001
    networks:
      - avr

  avr-sts-openai:
    image: agentvoiceresponse/avr-sts-openai
    platform: linux/x86_64
    container_name: avr-sts-openai
    restart: always
    environment:
      - PORT=6030
      - OPENAI_API_KEY=$OPENAI_API_KEY
      - OPENAI_MODEL=gpt-4o-realtime-preview
      - OPENAI_URL_INSTRUCTIONS=http://avr-instructions:3000/instructions
      - AMI_URL=${AMI_URL:-http://avr-ami:6006}
    # volumes: # uncomment if you want to use the custom tools
    #   - ./tools:/usr/src/app/tools
    networks:
      - avr

  avr-instructions:
    image: gcareri/avr-instructions
    platform: linux/x86_64
    container_name: avr-instructions
    restart: always
    environment:
      - PORT=3000
      - AMI_URL=${AMI_URL:-http://avr-ami:6006}
      - VITALPBX_URL=${VITALPBX_URL:-https://yourVitalPBX_URL/avr-agent.php?prompt=}
    networks:
      - avr

  avr-ami:
    image: agentvoiceresponse/avr-ami
    platform: linux/x86_64
    container_name: avr-ami
    restart: always
    environment:
      - PORT=6006
      - AMI_HOST=${AMI_HOST:-avr-asterisk}
      - AMI_PORT=${AMI_PORT:-5038}
      - AMI_USERNAME=${AMI_USERNAME:-avr}
      - AMI_PASSWORD=${AMI_PASSWORD:-avr}
    ports:
      - 6006:6006
    networks:
      - avr

networks:
  avr:
    name: avr
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/24

And here’s the relevant .env entries for OpenAI Realtime

# Container Ports
CORE_PORT_START=5000
ASR_PORT_START=6000
LLM_PORT_START=7000
TTS_PORT_START=8000
AMI_PORT=9000

# Network
AVR_NETWORK=avr

# AMI
AMI_HOST=your_vitalpbx_ip
AMI_PORT=5038
AMI_USERNAME= your_ami_user
AMI_PASSWORD= your_ami_password

# OPENAI
OPENAI_API_KEY=your_openai_key
OPENAI_MODEL=gpt-4o-mini-realtime
OPENAI_MAX_TOKENS=100
OPENAI_TEMPERATURE=0.0


I added a small PHP file as a test to respond to avr-agent’s prompt lookup query in /usr/share/vitalpbx/www/avr-agent.php

<?php
// avr-agent.php

if (!isset($_GET['prompt'])) {
    http_response_code(400);
    echo "Missing prompt parameter.";
    exit;
}

$prompt = $_GET['prompt'];

if (preg_match('/^T(\d+)_(\d+)$/', $prompt, $matches)) {
    $tenant    = $matches[1];
    $extension = $matches[2];
    $response  = "You are answering phone calls in English, greet the caller and let them know you are at extension $extension for tenant $tenant.";
} else {
    $response = "Invalid prompt format. Expected format: T{tenant}_{extension}.";
}

// Output
header('Content-Type: text/plain; charset=utf-8');
echo $response;

And last in VitalPBX I added custom destinations for the new extensions

To test I created custom destinations for 5001, 5002, and 5003. Setup an IVR with 1 routed to 5001, 2 to 5002, and 3 to 5003.

Calling in to the system you should now be able to select an extension and the talk to OpenAI Realtime. Note with this basic prompt OpenAI was not responding until I said something.

What’s really awesome about this is AVR Agent supports a lot of different TTS, ASR, and LLM services. Gcareri just got it working with Gemini a couple days ago as well. He’ll be working on support for transferring and ending calls with OpenAI which I think is the only major piece missing now.

5 Likes

Thanks so much, @Eli_Hunter, for your contribution and for sharing this! You implemented a really interesting integration.

One of the great things about AgentVoiceResponse (AVR) is its flexibility: you can choose from multiple TTS, ASR, and LLM services, deploy everything locally, in the cloud, or use a hybrid setup. This makes it easy to adapt to different projects and requirements, and integrate with existing Asterisk setups.

In the latest avr-sts-openai (1.5.0), we’ve added support for function calls. This means you can now implement custom logic directly from your AI agent. You can find the integration details here:

and learn how to use function calls here:

Happy experimenting and looking forward to seeing what everyone builds! :rocket:

2 Likes

hi @gcareri @Eli_Hunter

if you don’t mind, could you please provide an example of how to use avr_transfer and avr_hangup!

Hi Sir, @Eli_Hunter @gcareri

I setup on docker compose

avr-ami:
    image: agentvoiceresponse/avr-ami
    platform: linux/x86_64
    container_name: avr-ami
    restart: always
    environment:
      - PORT=6006
      - AMI_HOST=127.0.0.1
      - AMI_PORT=5038
      - AMI_USERNAME=agentvoiceresponse
      - AMI_PASSWORD=agentvoiceresponse
    ports:
      - 6006:6006
    networks:
      - avr

AMI USER

[agentvoiceresponse] ; this must be generated automatically
secret = agentvoiceresponse
deny = 0.0.0.0/0.0.0.0
permit= 0.0.0.0/0.0.0.0
read = all
write = all
writetimeout = 60000

repo*CLI> manager show user agentvoiceresponse

          username: agentvoiceresponse
            secret: <Set>
               ACL: yes
         read perm: system,call,log,verbose,command,agent,user,config,dtmf,reporting,cdr,dialplan,originate,agi,cc,aoc,test,security,message,all
        write perm: system,call,log,verbose,command,agent,user,config,dtmf,reporting,cdr,dialplan,originate,agi,cc,aoc,test,security,message,all
   displayconnects: no
allowmultiplelogin: yes
         Variables:
ACL: (unnamed)
---------------------------------------------
  0:  deny - 0.0.0.0/0.0.0.0
  1: allow - 0.0.0.0/0.0.0.0

docker compose status

root@repo:~/agentvoiceresponse# docker ps -a
CONTAINER ID   IMAGE                                 COMMAND                  CREATED         STATUS         PORTS                                         NAMES
89db473c10a3   agentvoiceresponse/avr-ami            "docker-entrypoint.s…"   5 minutes ago   Up 5 minutes   0.0.0.0:6006->6006/tcp, [::]:6006->6006/tcp   avr-ami
1785aa47dfa8   agentvoiceresponse/avr-core           "docker-entrypoint.s…"   5 minutes ago   Up 5 minutes   0.0.0.0:5001->5001/tcp, [::]:5001->5001/tcp   avr-core
e730ecf316f9   gcareri/avr-sts-openai-instructions   "docker-entrypoint.s…"   5 minutes ago   Up 5 minutes                                                 avr-sts-openai

If check docker logs avr-ami never connect to ami

root@repo:~/agentvoiceresponse# docker logs avr-ami
Asterisk Manager Interface listening on port 6006
Trying to reconnect to AMI in 10 seconds
Trying to reconnect to AMI in 20 seconds
Trying to reconnect to AMI in 30 seconds
Trying to reconnect to AMI in 40 seconds
Trying to reconnect to AMI in 50 seconds
Trying to reconnect to AMI in 60 seconds
Trying to reconnect to AMI in 60 seconds
Trying to reconnect to AMI in 60 seconds
Trying to reconnect to AMI in 60 seconds

asterisk -rx “manager show connected”

root@repo:~/agentvoiceresponse# asterisk  -r
Asterisk 20.15.0, Copyright (C) 1999 - 2025, Sangoma Technologies Corporation and others.
Created by Mark Spencer <[email protected]>
Asterisk comes with ABSOLUTELY NO WARRANTY; type 'core show warranty' for details.
This is free software, with components licensed under the GNU General Public
License version 2 and other licenses; you are welcome to redistribute it under
certain conditions. Type 'core show license' for details.
=========================================================================
Connected to Asterisk 20.15.0 currently running on repo (pid = 396579)
repo*CLI> manager show connected
  Username         IP Address                                               Start       Elapsed     FileDes   HttpCnt   ReadPerms   WritePerms
  astmanager       127.0.0.1                                                1754843200  2210487     42        0         0000008191  1073750015
1 users connected.

Odd, that does look good to me.
Can you see if you spot anything by running the containers without -d?

hi @x_mos

the tools avr_transfer and avr_hangup are already included by default in the avr-sts-openai integration.

When you start a call, OpenAI automatically loads these tools into the assistant you configured.

To use them, you simply need to set up the right prompt instructions. For example:

You are a virtual assistant. Your task is to assist users with their bills (internet phone bill, gas bill, or electricity bill).

If the user needs help with:
	•	An internet phone bill, use the transfer parameter "5002".
	•	A gas bill, use the transfer parameter "5003".
	•	An electricity bill, use the transfer parameter "5004".

After identifying the correct transfer parameter, call a function named "avr_transfer" with the transfer parameter.
Call a function named "avr_hangup" when the call is completed.

Here’s a demo video showing how it works:

If you want to configure a custom function (e.g., get_weather), check out the wiki.

Your setup looks good — the only thing I noticed is that AMI_HOST=127.0.0.1 might not be correct.
Maybe @Eli_Hunter can confirm how he set up the host.

Thanks for the collaboration.

Oh you know I do have mine set to connect to the public IP although I did just confirm manager is listening on all IPs so works on 127.0.0.1 and my WAN IP. It’s odd Asterisk shows the connection looks successful there.

Hi @Eli_Hunter @gcareri

The AMI has successfully connected from the avr-ami container. I need to use the eth0 IP as the AMI_HOST value.

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.199.233.50  netmask 255.255.255.192  broadcast 10.199.233.63
        inet6 fe80::216:3eff:fe09:7488  prefixlen 64  scopeid 0x20<link>
        ether 00:16:3e:09:74:88  txqueuelen 1000  (Ethernet)
        RX packets 2044427  bytes 1193128295 (1.1 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1452672  bytes 350419843 (334.1 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

repo*CLI> manager show connected
  Username         IP Address                                               Start       Elapsed     FileDes   HttpCnt   ReadPerms   WritePerms
  astmanager       127.0.0.1                                                1754843200  2290750     42        0         0000008191  1073750015
  agentvoicerespo  172.20.0.2                                               1757133647  303         9         0         2147483647  2147483647

The next question is, why does Spanish appear in the final transcription? while I use Indonesian!

Attaching to avr-ami, avr-sts-openai, avr-core
avr-ami           | Asterisk Manager Interface listening on port 6006
avr-core          | 2025-09-06 04:40:47 [INFO]: Server listening on port 5001
avr-sts-openai    | OpenAI Speech-to-Speech server running on port 6030
avr-core          | 2025-09-06 04:41:03 [INFO]: Client connected
avr-core          | 2025-09-06 04:41:03 [WARN]: AMBIENT_NOISE_FILE environment variable not set. Ambient noise disabled.
avr-core          | 2025-09-06 04:41:03 [INFO]: UUID packet received: 42acfc45-8fe0-4898-a18c-d479aeb04711
avr-sts-openai    | New audio stream received
avr-sts-openai    | Session UUID: 42acfc45-8fe0-4898-a18c-d479aeb04711
avr-sts-openai    | WebSocket connected to OpenAI
avr-sts-openai    | Received message type: session.created
avr-ami           | Calling ami variables action by uuid: 42acfc45-8fe0-4898-a18c-d479aeb04711
avr-sts-openai    | Received variables response: {
avr-sts-openai    |   channel: 'PJSIP/TELCO1-00000019',
avr-sts-openai    |   tenant_id: 'T1_',
avr-sts-openai    |   uuid: '42acfc45-8fe0-4898-a18c-d479aeb04711'
avr-sts-openai    | }
avr-sts-openai    | Audio streaming started
avr-sts-openai    | Received message type: input_audio_buffer.speech_stopped
avr-sts-openai    | Received message type: input_audio_buffer.committed
avr-sts-openai    | Received message type: conversation.item.created
avr-sts-openai    | Received message type: response.created
avr-sts-openai    | Received message type: response.output_item.added
avr-sts-openai    | Received message type: conversation.item.created
avr-sts-openai    | Received message type: response.content_part.added
avr-sts-openai    | Audio streaming completed
avr-sts-openai    | Final transcript: ¡Hola! ¿Qué tal?
avr-sts-openai    | Received message type: response.content_part.done
avr-sts-openai    | Received message type: response.output_item.done
avr-sts-openai    | Received message type: response.done
avr-sts-openai    | Received message type: rate_limits.updated
avr-sts-openai    | Audio streaming started
avr-sts-openai    | Received message type: input_audio_buffer.speech_stopped
avr-sts-openai    | Received message type: input_audio_buffer.committed
avr-sts-openai    | Received message type: conversation.item.created
avr-sts-openai    | Received message type: response.created
avr-sts-openai    | Received message type: response.output_item.added
avr-sts-openai    | Received message type: conversation.item.created
avr-sts-openai    | Received message type: response.content_part.added
avr-sts-openai    | Audio streaming completed
avr-sts-openai    | Final transcript: ¡Qué bien! Me alegro. ¿En qué te puedo ayudar hoy? 
avr-sts-openai    | Received message type: response.content_part.done
avr-sts-openai    | Received message type: response.output_item.done
avr-sts-openai    | Received message type: response.done
avr-sts-openai    | Received message type: rate_limits.updated
avr-sts-openai    | Audio streaming started
avr-sts-openai    | Received message type: input_audio_buffer.speech_stopped
avr-sts-openai    | Received message type: input_audio_buffer.committed
avr-sts-openai    | Received message type: conversation.item.created
avr-sts-openai    | Received message type: response.created
avr-sts-openai    | Received message type: response.output_item.added
avr-sts-openai    | Received message type: conversation.item.created
avr-sts-openai    | Received message type: response.content_part.added
avr-sts-openai    | Audio streaming completed
avr-sts-openai    | Final transcript: ¡Vaya! Eso suena muy bien. ¿Tienes algún plan especial para hoy o algo que te gustaría compartir?
avr-sts-openai    | Received message type: response.content_part.done
avr-sts-openai    | Received message type: response.output_item.done
avr-sts-openai    | Received message type: response.done
avr-sts-openai    | Received message type: rate_limits.updated
avr-core          | 2025-09-06 04:41:41 [INFO]: Socket Client disconnected
avr-core          | 2025-09-06 04:41:41 [INFO]: Terminate packet received
avr-core          | 2025-09-06 04:41:42 [INFO]: Streaming stopped
avr-core          | 2025-09-06 04:41:42 [INFO]: Client connection duration: 38.79 seconds
avr-core          | 2025-09-06 04:41:42 [INFO]: Streaming complete
avr-core          | 2025-09-06 04:41:42 [INFO]: STS streaming ended
avr-sts-openai    | WebSocket connection closed

Have you @Eli_Hunter successfully implemented avr_hangup and avr_transfer in the /usr/share/vitalpbx/www/avr-agent.php script? If so, please share it here.

Thanks

That’s awesome you got it working. Since you do have it on the WAN interface make sure to lock down that AMI user to 172.20.0.2 and I assume you don’t have any firewall rules allowing 5038 to be accessible from outside.

You have to be very specific with your prompt. For example I almost always include something like “reply only in the language you were spoken to”. Chatgpt actually comes up with pretty good prompts if you tell it what you’re trying to do, you’ll still need some testing and tweaking the prompt but it’ll get you a good sample baseline to work from.

I haven’t gotten hangup and transfer working yet, I’ll post back here once I do.

1 Like

Just a heads up the newest docker image isn’t working correctly passing the prompt, I updated the initial post to stick to 1.3.4 in case anybody is testing this. I’ll work with gcareri to make this universal and not just apply to vitalpbx so we don’t need a custom image.

1 Like

@gcareri has updated AVR-Agent to separate out the prompt logic retrieval from the integration so we don’t need a custom container for avr-sts-openai now. See updated directions in the first post but basically we have an avr-instructions container gcareri has made for VitalPBX installations.

I’ve also been able to get transfers working by changing the context demo to cos-all in the avr_tools/avr_transfer.js file.

For anybody that’s interested in making this better join the discord channel for avr-agent with the link from his page https://agentvoiceresponse.com/.
I’ve asked @gcareri about logging the transcripts and he has a plan for that.

Some tweaks I’d like to see are possibly using the AI prompts from the VitalPBX database by modifying the php file in the first post and maybe mounting some of the index.js files so we can edit them a bit easier.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.