rtpbench 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rtpbench/.dockerignore +4 -0
- rtpbench/Dockerfile +12 -0
- rtpbench/__init__.py +6 -0
- rtpbench/__main__.py +3 -0
- rtpbench/agent.py +746 -0
- rtpbench/analyze.py +520 -0
- rtpbench/bargein.py +272 -0
- rtpbench/bench.py +1069 -0
- rtpbench/caller.py +781 -0
- rtpbench/capture.py +33 -0
- rtpbench/carrier/compose.yaml +68 -0
- rtpbench/carrier/opensips.cfg +226 -0
- rtpbench/carrier/tls.cfg +25 -0
- rtpbench/carrier/tls.yaml +9 -0
- rtpbench/cases.py +1989 -0
- rtpbench/check.py +369 -0
- rtpbench/cli.py +616 -0
- rtpbench/clock.py +174 -0
- rtpbench/compose.yaml +55 -0
- rtpbench/corpus/README.md +55 -0
- rtpbench/corpus/cough-c1-0.wav +0 -0
- rtpbench/corpus/cough-c1-1.wav +0 -0
- rtpbench/corpus/cough-c2-0.wav +0 -0
- rtpbench/corpus/cough-c2-1.wav +0 -0
- rtpbench/corpus/cough-man-0.wav +0 -0
- rtpbench/corpus/cough-man-1.wav +0 -0
- rtpbench/corpus/cough-short-0.wav +0 -0
- rtpbench/corpus/cough-short-1.wav +0 -0
- rtpbench/corpus/cough-woman_three_times-0.wav +0 -0
- rtpbench/corpus/human-en-dr-0.wav +0 -0
- rtpbench/corpus/human-en-dr-1.wav +0 -0
- rtpbench/corpus/human-en-sc-0.wav +0 -0
- rtpbench/corpus/human-en-sc-1.wav +0 -0
- rtpbench/corpus/human-en-sc-2.wav +0 -0
- rtpbench/corpus/human-es-f012-0.wav +0 -0
- rtpbench/corpus/human-es-f012-1.wav +0 -0
- rtpbench/corpus/human-es-f012-2.wav +0 -0
- rtpbench/corpus/human-es-f016-0.wav +0 -0
- rtpbench/corpus/human-es-f016-1.wav +0 -0
- rtpbench/corpus/human-es-f016-2.wav +0 -0
- rtpbench/corpus/lines/en/interrupt-00.wav +0 -0
- rtpbench/corpus/lines/en/interrupt-01.wav +0 -0
- rtpbench/corpus/lines/en/interrupt-02.wav +0 -0
- rtpbench/corpus/lines/en/interrupt-03.wav +0 -0
- rtpbench/corpus/lines/en/prompt-00.wav +0 -0
- rtpbench/corpus/lines/en/prompt-01.wav +0 -0
- rtpbench/corpus/lines/en/prompt-02.wav +0 -0
- rtpbench/corpus/lines/en/prompt-03.wav +0 -0
- rtpbench/corpus/lines/en/prompt-04.wav +0 -0
- rtpbench/corpus/lines/en/prompt-05.wav +0 -0
- rtpbench/corpus/lines/en/prompt-06.wav +0 -0
- rtpbench/corpus/lines/en/prompt-07.wav +0 -0
- rtpbench/corpus/lines/en/prompt-08.wav +0 -0
- rtpbench/corpus/lines/en/prompt-09.wav +0 -0
- rtpbench/corpus/lines/en/prompt.wav +0 -0
- rtpbench/corpus/lines/en/transfer.wav +0 -0
- rtpbench/corpus/lines/en/turn-00.wav +0 -0
- rtpbench/corpus/lines/en/turn-01.wav +0 -0
- rtpbench/corpus/lines/en/turn-02.wav +0 -0
- rtpbench/corpus/lines/en/turn-03.wav +0 -0
- rtpbench/corpus/lines/en/turn-04.wav +0 -0
- rtpbench/corpus/lines/en/turn-05.wav +0 -0
- rtpbench/corpus/lines/en/turn-06.wav +0 -0
- rtpbench/corpus/lines/en/turn-07.wav +0 -0
- rtpbench/corpus/lines/en/turn-08.wav +0 -0
- rtpbench/corpus/lines/en/turn-09.wav +0 -0
- rtpbench/corpus/lines/en/turn-10.wav +0 -0
- rtpbench/corpus/lines/en/turn-11.wav +0 -0
- rtpbench/corpus/tts-ald-bc.wav +0 -0
- rtpbench/corpus/tts-ald-long.wav +0 -0
- rtpbench/corpus/tts-ald-s03.wav +0 -0
- rtpbench/corpus/tts-ald-s07.wav +0 -0
- rtpbench/corpus/tts-ald-s11.wav +0 -0
- rtpbench/corpus/tts-cori-bc.wav +0 -0
- rtpbench/corpus/tts-cori-long.wav +0 -0
- rtpbench/corpus/tts-cori-s01.wav +0 -0
- rtpbench/corpus/tts-cori-s05.wav +0 -0
- rtpbench/corpus/tts-cori-s09.wav +0 -0
- rtpbench/corpus/tts-davefx-bc.wav +0 -0
- rtpbench/corpus/tts-davefx-long.wav +0 -0
- rtpbench/corpus/tts-davefx-s00.wav +0 -0
- rtpbench/corpus/tts-davefx-s04.wav +0 -0
- rtpbench/corpus/tts-davefx-s08.wav +0 -0
- rtpbench/corpus/tts-libritts0-bc.wav +0 -0
- rtpbench/corpus/tts-libritts0-long.wav +0 -0
- rtpbench/corpus/tts-libritts0-s02.wav +0 -0
- rtpbench/corpus/tts-libritts0-s06.wav +0 -0
- rtpbench/corpus/tts-libritts0-s10.wav +0 -0
- rtpbench/corpus/tts-libritts3-bc.wav +0 -0
- rtpbench/corpus/tts-libritts3-long.wav +0 -0
- rtpbench/corpus/tts-libritts3-s03.wav +0 -0
- rtpbench/corpus/tts-libritts3-s07.wav +0 -0
- rtpbench/corpus/tts-libritts3-s11.wav +0 -0
- rtpbench/corpus/tts-ljspeech-bc.wav +0 -0
- rtpbench/corpus/tts-ljspeech-long.wav +0 -0
- rtpbench/corpus/tts-ljspeech-s00.wav +0 -0
- rtpbench/corpus/tts-ljspeech-s04.wav +0 -0
- rtpbench/corpus/tts-ljspeech-s08.wav +0 -0
- rtpbench/corpus/tts-sharvardf-bc.wav +0 -0
- rtpbench/corpus/tts-sharvardf-long.wav +0 -0
- rtpbench/corpus/tts-sharvardf-s02.wav +0 -0
- rtpbench/corpus/tts-sharvardf-s06.wav +0 -0
- rtpbench/corpus/tts-sharvardf-s10.wav +0 -0
- rtpbench/corpus/tts-sharvardm-bc.wav +0 -0
- rtpbench/corpus/tts-sharvardm-long.wav +0 -0
- rtpbench/corpus/tts-sharvardm-s01.wav +0 -0
- rtpbench/corpus/tts-sharvardm-s05.wav +0 -0
- rtpbench/corpus/tts-sharvardm-s09.wav +0 -0
- rtpbench/detect.py +145 -0
- rtpbench/g711.py +82 -0
- rtpbench/mock/compose.yaml +18 -0
- rtpbench/pcap.py +106 -0
- rtpbench/progress.py +184 -0
- rtpbench/realtime.py +1015 -0
- rtpbench/report.py +450 -0
- rtpbench/rtp.py +166 -0
- rtpbench/sip.py +653 -0
- rtpbench/speech.py +100 -0
- rtpbench/speechcal.py +367 -0
- rtpbench/srtp.py +191 -0
- rtpbench/targets/aivc/aivc.ini +25 -0
- rtpbench/targets/aivc/compose.yaml +72 -0
- rtpbench/targets/aivc/constraints.txt +52 -0
- rtpbench/targets/aivc/opensips.cfg +45 -0
- rtpbench/targets/ava/ai-agent.yaml +275 -0
- rtpbench/targets/ava/ari.conf +7 -0
- rtpbench/targets/ava/compose.yaml +128 -0
- rtpbench/targets/ava/constraints.txt +75 -0
- rtpbench/targets/ava/extensions.conf +10 -0
- rtpbench/targets/ava/http.conf +4 -0
- rtpbench/targets/ava/pjsip.conf +19 -0
- rtpbench/targets/ava/websocket_client.conf +15 -0
- rtpbench/targets/livekit/agent.py +111 -0
- rtpbench/targets/livekit/compose.yaml +86 -0
- rtpbench/targets/livekit/constraints.txt +72 -0
- rtpbench/tls.py +284 -0
- rtpbench/uri/advertise.yaml +7 -0
- rtpbench/uri/network.yaml +14 -0
- rtpbench/voice.py +18 -0
- rtpbench-0.3.0.dist-info/METADATA +123 -0
- rtpbench-0.3.0.dist-info/RECORD +144 -0
- rtpbench-0.3.0.dist-info/WHEEL +4 -0
- rtpbench-0.3.0.dist-info/entry_points.txt +2 -0
- rtpbench-0.3.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# AI Voice Connector CE settings for rtpbench: the openai flavor only, as upstream's example sets
|
|
2
|
+
# it (voice, instructions, welcome message), plus a transfer target so the refer cases can run:
|
|
3
|
+
# the operator (rtpbench's transfer target, compose.yaml).
|
|
4
|
+
# The key and URL come from the environment (OPENAI_API_KEY, OPENAI_URL).
|
|
5
|
+
[opensips]
|
|
6
|
+
ip = 127.0.0.1
|
|
7
|
+
port = 8080
|
|
8
|
+
|
|
9
|
+
[engine]
|
|
10
|
+
event_ip = 127.0.0.1
|
|
11
|
+
|
|
12
|
+
[openai]
|
|
13
|
+
voice = alloy
|
|
14
|
+
instructions = You are a helpful assistant.
|
|
15
|
+
welcome_message = Hello! How can I help you?
|
|
16
|
+
transfer_to = sip:operator@operator
|
|
17
|
+
|
|
18
|
+
[deepgram]
|
|
19
|
+
disabled = true
|
|
20
|
+
|
|
21
|
+
[deepgram_native]
|
|
22
|
+
disabled = true
|
|
23
|
+
|
|
24
|
+
[azure]
|
|
25
|
+
disabled = true
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# OpenSIPS AI Voice Connector CE (openai flavor) as a target, merged over ../../compose.yaml.
|
|
2
|
+
# `target` owns the network namespace (agentnet, plus egress to the AI engine); OpenSIPS and the
|
|
3
|
+
# connector share it, as upstream's example shares the host's. rtpbench routes the caller via
|
|
4
|
+
# the router, so SIP and RTP cross netem. Needs OPENAI_API_KEY (and OPENAI_URL for a gateway).
|
|
5
|
+
# Paths are relative to the first compose file's directory.
|
|
6
|
+
services:
|
|
7
|
+
target:
|
|
8
|
+
image: ${RTPBENCH_IMAGE:?}
|
|
9
|
+
cap_add: [NET_ADMIN]
|
|
10
|
+
init: true
|
|
11
|
+
depends_on: [router]
|
|
12
|
+
networks: [agentnet, egress]
|
|
13
|
+
command: sleep infinity
|
|
14
|
+
|
|
15
|
+
opensips:
|
|
16
|
+
image: opensips/opensips:3.6@sha256:81a7fa3c7bdbceaadb7cb869ff7ac3f34efb2d95cd7fecb3b689d7b6b1eb0672
|
|
17
|
+
network_mode: service:target
|
|
18
|
+
depends_on: [target]
|
|
19
|
+
volumes: ["./targets/aivc/opensips.cfg:/etc/opensips/opensips.cfg:ro"]
|
|
20
|
+
healthcheck:
|
|
21
|
+
test: opensips-cli -x mi sr_get_status core | grep -q running
|
|
22
|
+
interval: 1s
|
|
23
|
+
retries: 20
|
|
24
|
+
|
|
25
|
+
engine:
|
|
26
|
+
build:
|
|
27
|
+
context: https://github.com/OpenSIPS/opensips-ai-voice-connector-ce.git#b95f5afa99301e5d6ee0643dcea73b1a457b6286
|
|
28
|
+
# upstream's docker/Dockerfile, with the base image and every installed package pinned
|
|
29
|
+
# (upstream pins only its direct requirements)
|
|
30
|
+
additional_contexts: { pins: ./targets/aivc }
|
|
31
|
+
dockerfile_inline: |
|
|
32
|
+
FROM python:3.14-trixie@sha256:446efb4225200a0a196707aa7953898b37962fd3b8859a636035de3af657081e
|
|
33
|
+
WORKDIR /app
|
|
34
|
+
COPY . /app/
|
|
35
|
+
COPY --from=pins constraints.txt /tmp/constraints.txt
|
|
36
|
+
RUN pip install --no-cache-dir -r requirements.txt -c /tmp/constraints.txt
|
|
37
|
+
CMD ["python3", "-u", "src/main.py"]
|
|
38
|
+
image: ${RTPBENCH_REGISTRY:?}/rtpbench-aivc:b95f5af${RTPBENCH_BUILD_ENGINE:-}
|
|
39
|
+
network_mode: service:target
|
|
40
|
+
depends_on:
|
|
41
|
+
opensips: { condition: service_healthy }
|
|
42
|
+
volumes: ["./targets/aivc/aivc.ini:/app/cfg/aivc.ini:ro"]
|
|
43
|
+
environment:
|
|
44
|
+
CONFIG_FILE: /app/cfg/aivc.ini
|
|
45
|
+
OPENAI_API_KEY: ${OPENAI_API_KEY:?set OPENAI_API_KEY}
|
|
46
|
+
OPENAI_URL: ${OPENAI_URL:-wss://api.openai.com/v1/realtime?model=gpt-realtime-2.1}
|
|
47
|
+
# upstream's default; empty for a model without reasoning (gpt-realtime-1.5), as its docs say
|
|
48
|
+
OPENAI_REASONING_EFFORT: ${OPENAI_REASONING_EFFORT-low}
|
|
49
|
+
# ready once OpenSIPS lists it as the subscriber for call events
|
|
50
|
+
healthcheck:
|
|
51
|
+
test:
|
|
52
|
+
- CMD
|
|
53
|
+
- python3
|
|
54
|
+
- -c
|
|
55
|
+
- >-
|
|
56
|
+
import json, socket; s = socket.socket(2, 2); s.settimeout(1);
|
|
57
|
+
s.sendto(json.dumps({"jsonrpc": "2.0", "id": 1, "method": "subscribers_list",
|
|
58
|
+
"params": ["E_UA_SESSION"]}).encode(), ("127.0.0.1", 8080));
|
|
59
|
+
assert json.loads(s.recv(65535))["result"]["Event"]["Subscribers"]
|
|
60
|
+
interval: 1s
|
|
61
|
+
retries: 30
|
|
62
|
+
# SDP carries the address facing the caller (the router's side), not the egress one
|
|
63
|
+
command:
|
|
64
|
+
- sh
|
|
65
|
+
- -c
|
|
66
|
+
- >-
|
|
67
|
+
RTP_IP=$$(python3 -c 'import socket as s; u = s.socket(2, 2);
|
|
68
|
+
u.connect((s.gethostbyname("router"), 9)); print(u.getsockname()[0])')
|
|
69
|
+
exec python3 -u src/main.py
|
|
70
|
+
|
|
71
|
+
networks:
|
|
72
|
+
egress:
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
aioice==0.10.2
|
|
2
|
+
aiortc==1.15.0
|
|
3
|
+
annotated-types==0.8.0
|
|
4
|
+
anyio==4.15.1
|
|
5
|
+
attrs==26.1.0
|
|
6
|
+
av==17.1.0
|
|
7
|
+
azure-cognitiveservices-speech==1.51.2
|
|
8
|
+
azure-core==1.41.0
|
|
9
|
+
certifi==2026.7.22
|
|
10
|
+
cffi==2.1.1
|
|
11
|
+
charset-normalizer==3.5.2
|
|
12
|
+
click==8.5.0
|
|
13
|
+
cryptography==50.0.2
|
|
14
|
+
deepgram-sdk==7.8.1
|
|
15
|
+
dnspython==2.9.0
|
|
16
|
+
google-crc32c==1.9.0
|
|
17
|
+
h11==0.16.0
|
|
18
|
+
httpcore==1.0.9
|
|
19
|
+
httpcore2==2.13.1
|
|
20
|
+
httpx==0.28.1
|
|
21
|
+
httpx2==2.13.1
|
|
22
|
+
idna==3.20
|
|
23
|
+
ifaddr==0.2.0
|
|
24
|
+
jiter==0.17.0
|
|
25
|
+
jsonschema==4.26.0
|
|
26
|
+
jsonschema-specifications==2025.9.1
|
|
27
|
+
mcp==2.3.0
|
|
28
|
+
mcp-types==2.3.0
|
|
29
|
+
openai==3.11.0
|
|
30
|
+
opensips==0.1.10
|
|
31
|
+
opentelemetry-api==1.45.1
|
|
32
|
+
pycparser==3.11
|
|
33
|
+
pydantic==2.14.1
|
|
34
|
+
pydantic_core==2.50.1
|
|
35
|
+
pyee==14.0.0
|
|
36
|
+
PyJWT==2.15.1
|
|
37
|
+
pylibsrtp==1.0.0
|
|
38
|
+
pyOpenSSL==26.4.0
|
|
39
|
+
python-multipart==0.0.32
|
|
40
|
+
referencing==0.37.0
|
|
41
|
+
requests==2.34.2
|
|
42
|
+
rpds-py==2026.9.1
|
|
43
|
+
sipmessage==0.7.1
|
|
44
|
+
sniffio==1.3.1
|
|
45
|
+
sse-starlette==3.5.0
|
|
46
|
+
starlette==1.8.0
|
|
47
|
+
truststore==0.10.5
|
|
48
|
+
typing-inspection==0.4.4
|
|
49
|
+
typing_extensions==4.16.0
|
|
50
|
+
urllib3==2.8.0
|
|
51
|
+
uvicorn==0.54.0
|
|
52
|
+
websockets==17.1
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
# From OpenSIPS AI Voice Connector CE (c) SIP Point Consulting SRL, examples/simple/b2b/opensips.cfg
|
|
3
|
+
# at b95f5af, with its variables filled in (SIP on 5060, MI on 8080). Under the GPL like upstream.
|
|
4
|
+
|
|
5
|
+
log_level=3
|
|
6
|
+
xlog_level=2
|
|
7
|
+
stderror_enabled=yes
|
|
8
|
+
syslog_enabled=no
|
|
9
|
+
|
|
10
|
+
socket=udp:*:5060
|
|
11
|
+
socket=tcp:*:5060
|
|
12
|
+
|
|
13
|
+
mpath="/usr/lib/x86_64-linux-gnu/opensips/modules/"
|
|
14
|
+
|
|
15
|
+
loadmodule "tm.so"
|
|
16
|
+
loadmodule "rr.so"
|
|
17
|
+
loadmodule "sipmsgops.so"
|
|
18
|
+
loadmodule "signaling.so"
|
|
19
|
+
loadmodule "cfgutils.so"
|
|
20
|
+
loadmodule "mi_fifo.so"
|
|
21
|
+
loadmodule "proto_tcp.so"
|
|
22
|
+
loadmodule "proto_udp.so"
|
|
23
|
+
loadmodule "sl.so"
|
|
24
|
+
loadmodule "event_datagram.so"
|
|
25
|
+
loadmodule "mi_datagram.so"
|
|
26
|
+
loadmodule "b2b_entities.so"
|
|
27
|
+
|
|
28
|
+
modparam("mi_fifo", "fifo_name", "/tmp/opensips_fifo")
|
|
29
|
+
|
|
30
|
+
modparam("tm", "fr_timeout", 2)
|
|
31
|
+
modparam("tm", "fr_inv_timeout", 3)
|
|
32
|
+
modparam("tm", "restart_fr_on_each_reply", 0)
|
|
33
|
+
modparam("tm", "onreply_avp_mode", 1)
|
|
34
|
+
|
|
35
|
+
modparam("mi_datagram", "socket_name", "udp:127.0.0.1:8080")
|
|
36
|
+
|
|
37
|
+
route {
|
|
38
|
+
if (!is_method("INVITE") || has_totag()) {
|
|
39
|
+
send_reply(405, "Method not allowed!\n");
|
|
40
|
+
exit;
|
|
41
|
+
}
|
|
42
|
+
ua_session_server_init($var(b2b_key), "drbh");
|
|
43
|
+
xlog("Started new call for $var(b2b_key)\n");
|
|
44
|
+
exit;
|
|
45
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# AVA's golden OpenAI Realtime configuration (config/ai-agent.golden-openai.yaml at
|
|
2
|
+
# ba52df92bc1cad2c1f277034e1423b80c8f50be4), with rtpbench's edits marked "rtpbench:".
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2025 Asterisk AI Voice Agent contributors
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
|
7
|
+
# and associated documentation files (the "Software"), to deal in the Software without
|
|
8
|
+
# restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
# distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
|
10
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in all copies or
|
|
13
|
+
# substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
|
16
|
+
# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
18
|
+
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
20
|
+
|
|
21
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
22
|
+
# Golden Baseline #1: OpenAI Realtime - Cloud Monolithic Agent
|
|
23
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
24
|
+
#
|
|
25
|
+
# VALIDATED CONFIGURATION - Production-ready
|
|
26
|
+
#
|
|
27
|
+
# Requirements:
|
|
28
|
+
# - OPENAI_API_KEY in .env file
|
|
29
|
+
# - Asterisk 18+ with ARI and AudioSocket modules
|
|
30
|
+
#
|
|
31
|
+
# Performance:
|
|
32
|
+
# - Response time: <2 seconds (typically 0.5-1.5s)
|
|
33
|
+
# - Audio quality: Excellent (native 24kHz output)
|
|
34
|
+
# - Cost: ~$0.06 per minute (OpenAI Realtime pricing)
|
|
35
|
+
#
|
|
36
|
+
# Best for:
|
|
37
|
+
# - Quick setup and deployment
|
|
38
|
+
# - Modern, natural-sounding conversations
|
|
39
|
+
# - Enterprise deployments requiring reliability
|
|
40
|
+
#
|
|
41
|
+
# For detailed parameter explanations, see: docs/Configuration-Reference.md
|
|
42
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
43
|
+
|
|
44
|
+
# Active Configuration
|
|
45
|
+
config_version: 6
|
|
46
|
+
active_pipeline: local_hybrid # Don't change - uses provider override via contexts
|
|
47
|
+
default_provider: openai_realtime
|
|
48
|
+
|
|
49
|
+
# Audio Transport - AudioSocket for OpenAI Realtime
|
|
50
|
+
# rtpbench: AudioSocket as shipped, ExternalMedia (RTP) or WebSocket, from
|
|
51
|
+
# targets/ava/compose.yaml
|
|
52
|
+
audio_transport: ${AVA_TRANSPORT}
|
|
53
|
+
audiosocket:
|
|
54
|
+
format: slin
|
|
55
|
+
host: 0.0.0.0
|
|
56
|
+
port: 8090
|
|
57
|
+
# rtpbench: WebSocket's listener, as docs/WebSocket-Transport.md configures it on one host,
|
|
58
|
+
# matching targets/ava/websocket_client.conf
|
|
59
|
+
websocket_media:
|
|
60
|
+
connection_mode: asterisk_outbound
|
|
61
|
+
connection_name: aava_media
|
|
62
|
+
bind_host: 127.0.0.1
|
|
63
|
+
advertise_host: 127.0.0.1
|
|
64
|
+
port: 8787
|
|
65
|
+
path: /media
|
|
66
|
+
format_policy: profile
|
|
67
|
+
fallback_format: ulaw
|
|
68
|
+
control_format: json
|
|
69
|
+
direction: both
|
|
70
|
+
allowed_remote_hosts: [127.0.0.1]
|
|
71
|
+
auth:
|
|
72
|
+
required: true
|
|
73
|
+
username: aava_media
|
|
74
|
+
password_env: ASTERISK_MEDIA_WS_PASSWORD
|
|
75
|
+
tls:
|
|
76
|
+
enabled: false
|
|
77
|
+
|
|
78
|
+
# Playback Mode
|
|
79
|
+
downstream_mode: stream
|
|
80
|
+
|
|
81
|
+
# Contexts - Use demo_openai context to activate OpenAI Realtime
|
|
82
|
+
contexts:
|
|
83
|
+
default:
|
|
84
|
+
greeting: Hello, I am a Voice Assistant. How can I help you today?
|
|
85
|
+
profile: openai_realtime_24k_${AVA_TRANSPORT} # rtpbench: the transport's profile below
|
|
86
|
+
prompt: You are a concise voice assistant. Respond clearly and keep answers under 20 words unless more detail is requested.
|
|
87
|
+
provider: openai_realtime
|
|
88
|
+
# rtpbench: the transfer tool, to the destination below
|
|
89
|
+
tools:
|
|
90
|
+
- blind_transfer
|
|
91
|
+
demo_openai:
|
|
92
|
+
greeting: "Hi! I'm Ava running on OpenAI Realtime API. I'm fast and natural-sounding. What would you like to know about this project?"
|
|
93
|
+
prompt: "You are Ava (Asterisk Voice Agent) demonstrating OpenAI Realtime. Be helpful, conversational, and explain features clearly in 5-10 sentences."
|
|
94
|
+
profile: openai_realtime_24k_${AVA_TRANSPORT} # rtpbench: the transport's profile below
|
|
95
|
+
provider: openai_realtime
|
|
96
|
+
|
|
97
|
+
# Barge-In - Enable for natural interruption
|
|
98
|
+
barge_in:
|
|
99
|
+
enabled: true
|
|
100
|
+
energy_threshold: 700
|
|
101
|
+
initial_protection_ms: 100
|
|
102
|
+
min_ms: 150
|
|
103
|
+
post_tts_end_protection_ms: 100
|
|
104
|
+
|
|
105
|
+
# VAD Configuration
|
|
106
|
+
vad:
|
|
107
|
+
enhanced_enabled: true
|
|
108
|
+
fallback_buffer_size: 128000
|
|
109
|
+
fallback_enabled: true
|
|
110
|
+
fallback_interval_ms: 4000
|
|
111
|
+
max_utterance_duration_ms: 10000
|
|
112
|
+
min_utterance_duration_ms: 600
|
|
113
|
+
use_provider_vad: false
|
|
114
|
+
utterance_padding_ms: 200
|
|
115
|
+
webrtc_aggressiveness: 1
|
|
116
|
+
webrtc_end_silence_frames: 50
|
|
117
|
+
webrtc_start_frames: 3
|
|
118
|
+
|
|
119
|
+
# Streaming Configuration
|
|
120
|
+
streaming:
|
|
121
|
+
chunk_size_ms: 20
|
|
122
|
+
connection_timeout_ms: 120000
|
|
123
|
+
continuous_stream: true
|
|
124
|
+
empty_backoff_ticks_max: 5
|
|
125
|
+
fallback_timeout_ms: 8000
|
|
126
|
+
greeting_min_start_ms: 40
|
|
127
|
+
jitter_buffer_ms: 950
|
|
128
|
+
keepalive_interval_ms: 5000
|
|
129
|
+
low_watermark_ms: 80
|
|
130
|
+
min_start_ms: 120
|
|
131
|
+
normalizer:
|
|
132
|
+
enabled: true
|
|
133
|
+
max_gain_db: 18.0
|
|
134
|
+
target_rms: 1400
|
|
135
|
+
provider_grace_ms: 500
|
|
136
|
+
sample_rate: 8000
|
|
137
|
+
|
|
138
|
+
# Audio Profiles - openai_realtime_24k for native 24kHz audio
|
|
139
|
+
profiles:
|
|
140
|
+
default: telephony_responsive
|
|
141
|
+
# rtpbench: the golden openai_realtime_24k, as shipped for AudioSocket
|
|
142
|
+
openai_realtime_24k_audiosocket:
|
|
143
|
+
chunk_ms: 20
|
|
144
|
+
idle_cutoff_ms: 0
|
|
145
|
+
internal_rate_hz: 24000
|
|
146
|
+
provider_pref:
|
|
147
|
+
input_encoding: pcm16
|
|
148
|
+
input_sample_rate_hz: 24000
|
|
149
|
+
output_encoding: pcm16
|
|
150
|
+
output_sample_rate_hz: 24000
|
|
151
|
+
transport_out:
|
|
152
|
+
encoding: slin
|
|
153
|
+
sample_rate_hz: 8000
|
|
154
|
+
# rtpbench: the same for WebSocket, which takes its wire format (slin) from the profile
|
|
155
|
+
openai_realtime_24k_websocket:
|
|
156
|
+
chunk_ms: 20
|
|
157
|
+
idle_cutoff_ms: 0
|
|
158
|
+
internal_rate_hz: 24000
|
|
159
|
+
provider_pref:
|
|
160
|
+
input_encoding: pcm16
|
|
161
|
+
input_sample_rate_hz: 24000
|
|
162
|
+
output_encoding: pcm16
|
|
163
|
+
output_sample_rate_hz: 24000
|
|
164
|
+
transport_out:
|
|
165
|
+
encoding: slin
|
|
166
|
+
sample_rate_hz: 8000
|
|
167
|
+
# rtpbench: the same for ExternalMedia, but ulaw: slin goes out as PCMU, and the caller hears
|
|
168
|
+
# noise
|
|
169
|
+
openai_realtime_24k_externalmedia:
|
|
170
|
+
chunk_ms: 20
|
|
171
|
+
idle_cutoff_ms: 0
|
|
172
|
+
internal_rate_hz: 24000
|
|
173
|
+
provider_pref:
|
|
174
|
+
input_encoding: pcm16
|
|
175
|
+
input_sample_rate_hz: 24000
|
|
176
|
+
output_encoding: pcm16
|
|
177
|
+
output_sample_rate_hz: 24000
|
|
178
|
+
transport_out:
|
|
179
|
+
encoding: ulaw
|
|
180
|
+
sample_rate_hz: 8000
|
|
181
|
+
telephony_responsive:
|
|
182
|
+
chunk_ms: auto
|
|
183
|
+
idle_cutoff_ms: 600
|
|
184
|
+
internal_rate_hz: 8000
|
|
185
|
+
provider_pref:
|
|
186
|
+
input_encoding: mulaw
|
|
187
|
+
input_sample_rate_hz: 8000
|
|
188
|
+
output_encoding: mulaw
|
|
189
|
+
output_sample_rate_hz: 8000
|
|
190
|
+
transport_out:
|
|
191
|
+
encoding: slin
|
|
192
|
+
sample_rate_hz: 8000
|
|
193
|
+
|
|
194
|
+
# Provider Configuration - OpenAI Realtime
|
|
195
|
+
providers:
|
|
196
|
+
openai_realtime:
|
|
197
|
+
# rtpbench: base_url, model and the pacer from targets/ava/compose.yaml
|
|
198
|
+
base_url: ${OPENAI_BASE_URL}
|
|
199
|
+
egress_pacer_enabled: ${AVA_EGRESS_PACER}
|
|
200
|
+
egress_pacer_warmup_ms: 320
|
|
201
|
+
enabled: true
|
|
202
|
+
greeting: ${OPENAI_GREETING:-Hello, how can I help you today?}
|
|
203
|
+
input_encoding: ulaw
|
|
204
|
+
input_sample_rate_hz: 8000
|
|
205
|
+
instructions: You are a concise voice assistant. Respond clearly and keep answers under 20 words unless more detail is requested.
|
|
206
|
+
api_version: ga
|
|
207
|
+
model: ${OPENAI_MODEL}
|
|
208
|
+
organization: ''
|
|
209
|
+
output_encoding: linear16
|
|
210
|
+
output_sample_rate_hz: 24000
|
|
211
|
+
provider_input_encoding: linear16
|
|
212
|
+
provider_input_sample_rate_hz: 24000
|
|
213
|
+
response_modalities:
|
|
214
|
+
- audio
|
|
215
|
+
- text
|
|
216
|
+
target_encoding: mulaw
|
|
217
|
+
target_sample_rate_hz: 8000
|
|
218
|
+
turn_detection:
|
|
219
|
+
create_response: true
|
|
220
|
+
prefix_padding_ms: 200
|
|
221
|
+
silence_duration_ms: 200
|
|
222
|
+
threshold: 0.5
|
|
223
|
+
type: server_vad
|
|
224
|
+
voice: alloy
|
|
225
|
+
|
|
226
|
+
# LLM Fallback Configuration
|
|
227
|
+
llm:
|
|
228
|
+
initial_greeting: Hello, how can I help you today?
|
|
229
|
+
prompt: Voice assistant. Answer in 5-8 words. Be direct. Expand only if asked.
|
|
230
|
+
|
|
231
|
+
# Pipelines - Required but not used by monolithic OpenAI provider
|
|
232
|
+
pipelines:
|
|
233
|
+
local_hybrid:
|
|
234
|
+
llm: openai_llm
|
|
235
|
+
options:
|
|
236
|
+
llm:
|
|
237
|
+
base_url: https://api.openai.com/v1
|
|
238
|
+
max_tokens: 150
|
|
239
|
+
model: gpt-4o-mini
|
|
240
|
+
temperature: 0.7
|
|
241
|
+
stt:
|
|
242
|
+
chunk_ms: 160
|
|
243
|
+
mode: stt
|
|
244
|
+
stream_format: pcm16_16k
|
|
245
|
+
streaming: true
|
|
246
|
+
tts:
|
|
247
|
+
format:
|
|
248
|
+
encoding: mulaw
|
|
249
|
+
sample_rate: 8000
|
|
250
|
+
stt: local_stt
|
|
251
|
+
tts: local_tts
|
|
252
|
+
|
|
253
|
+
# Asterisk Configuration
|
|
254
|
+
asterisk:
|
|
255
|
+
app_name: asterisk-ai-voice-agent
|
|
256
|
+
|
|
257
|
+
# rtpbench: transfers go to the operator through targets/ava/extensions.conf
|
|
258
|
+
tools:
|
|
259
|
+
transfer:
|
|
260
|
+
enabled: true
|
|
261
|
+
destinations:
|
|
262
|
+
operator:
|
|
263
|
+
description: An operator, a person
|
|
264
|
+
dialplan_context: transfer
|
|
265
|
+
target: operator
|
|
266
|
+
type: extension
|
|
267
|
+
|
|
268
|
+
# ExternalMedia (not used by AudioSocket, but required by config schema)
|
|
269
|
+
external_media:
|
|
270
|
+
codec: ulaw
|
|
271
|
+
direction: both
|
|
272
|
+
port_range: 18080:18099
|
|
273
|
+
# rtpbench: Asterisk sends to this address, so not 0.0.0.0 (it shares the engine's netns)
|
|
274
|
+
rtp_host: 127.0.0.1
|
|
275
|
+
rtp_port: 18080
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Asterisk + AVA (Asterisk AI Voice Agent) as a target, merged over ../../compose.yaml. AVA is an
|
|
2
|
+
# ARI app for an existing Asterisk; its compose runs the engine on the host's network next to
|
|
3
|
+
# it. Here Asterisk and the engine share the network namespace of `target` (agentnet, plus
|
|
4
|
+
# egress to the AI engine), and the engine reaches ARI and the call audio (AudioSocket,
|
|
5
|
+
# ExternalMedia RTP or Asterisk's WebSocket channel, AVA_TRANSPORT) on 127.0.0.1.
|
|
6
|
+
# rtpbench routes the caller via the router, so SIP and RTP cross netem. Needs OPENAI_API_KEY
|
|
7
|
+
# (and OPENAI_BASE_URL for another Realtime endpoint). Paths are relative to the first compose
|
|
8
|
+
# file's directory.
|
|
9
|
+
services:
|
|
10
|
+
target:
|
|
11
|
+
image: ${RTPBENCH_IMAGE:?}
|
|
12
|
+
cap_add: [NET_ADMIN]
|
|
13
|
+
init: true
|
|
14
|
+
depends_on: [router]
|
|
15
|
+
networks: [agentnet, egress]
|
|
16
|
+
command: sleep infinity
|
|
17
|
+
|
|
18
|
+
asterisk:
|
|
19
|
+
build:
|
|
20
|
+
context: ./targets/ava
|
|
21
|
+
# andrius/asterisk without the AudioSocket modules (menuselect leaves them out): built
|
|
22
|
+
# from the same source and ABI options (OPTIONAL_API), and added
|
|
23
|
+
dockerfile_inline: |
|
|
24
|
+
FROM debian:trixie@sha256:913f6706df59a68922d1dd08f78c2476560a8d367897200a6005b00e5f67c2d5 AS modules
|
|
25
|
+
# the Debian snapshot this base image was built from, so every package version is fixed
|
|
26
|
+
RUN sed -i -e 's|^URIs: http://deb.debian.org/debian$$|URIs: http://snapshot.debian.org/archive/debian/20261005T000000Z|' \
|
|
27
|
+
-e 's|^URIs: http://deb.debian.org/debian-security$$|URIs: http://snapshot.debian.org/archive/debian-security/20261005T000000Z|' \
|
|
28
|
+
/etc/apt/sources.list.d/debian.sources \
|
|
29
|
+
&& apt-get -o Acquire::Check-Valid-Until=false update \
|
|
30
|
+
&& apt-get install -y --no-install-recommends ca-certificates \
|
|
31
|
+
build-essential curl libedit-dev libjansson-dev libncurses-dev libsqlite3-dev \
|
|
32
|
+
libssl-dev libxml2-dev uuid-dev pkg-config libcurl4-openssl-dev libsrtp2-dev
|
|
33
|
+
WORKDIR /usr/src/asterisk
|
|
34
|
+
RUN curl -fsSL -o /tmp/a.tar.gz https://downloads.asterisk.org/pub/telephony/certified-asterisk/releases/asterisk-certified-22.8-cert4.tar.gz \
|
|
35
|
+
&& echo "a41a7007d420119255017bbf15759a4204fb45915b1ce5f0dc7622ecaf498c34 /tmp/a.tar.gz" | sha256sum -c - \
|
|
36
|
+
&& tar --strip-components=1 -xzf /tmp/a.tar.gz && rm /tmp/a.tar.gz
|
|
37
|
+
RUN ./configure --with-ssl=ssl --with-crypto --without-pjproject-bundled \
|
|
38
|
+
--without-pjproject > /dev/null && make menuselect.makeopts > /dev/null \
|
|
39
|
+
&& menuselect/menuselect --disable BUILD_NATIVE --enable BETTER_BACKTRACES \
|
|
40
|
+
--enable res_audiosocket --enable chan_audiosocket --enable app_audiosocket \
|
|
41
|
+
menuselect.makeopts
|
|
42
|
+
RUN make -j"$$(nproc)" res channels apps > /dev/null \
|
|
43
|
+
&& grep -q '"OPTIONAL_API"' include/asterisk/buildopts.h
|
|
44
|
+
|
|
45
|
+
FROM andrius/asterisk:22.8-cert4_debian-trixie@sha256:f3bd01300f0c36c8a7ea5bd9472c4b906d0f85410c3ea429166a0fedac6ad3ee
|
|
46
|
+
COPY --from=modules /usr/src/asterisk/res/res_audiosocket.so \
|
|
47
|
+
/usr/src/asterisk/channels/chan_audiosocket.so \
|
|
48
|
+
/usr/src/asterisk/apps/app_audiosocket.so /usr/lib/asterisk/modules/
|
|
49
|
+
image: ${RTPBENCH_REGISTRY:?}/rtpbench-ava-asterisk:22.8-cert4${RTPBENCH_BUILD_ASTERISK:-}
|
|
50
|
+
network_mode: service:target
|
|
51
|
+
depends_on: [target]
|
|
52
|
+
volumes:
|
|
53
|
+
- ./targets/ava/pjsip.conf:/etc/asterisk/pjsip.conf:ro
|
|
54
|
+
- ./targets/ava/extensions.conf:/etc/asterisk/extensions.conf:ro
|
|
55
|
+
- ./targets/ava/ari.conf:/etc/asterisk/ari.conf:ro
|
|
56
|
+
- ./targets/ava/http.conf:/etc/asterisk/http.conf:ro
|
|
57
|
+
- ./targets/ava/websocket_client.conf:/etc/asterisk/websocket_client.conf:ro
|
|
58
|
+
healthcheck:
|
|
59
|
+
test:
|
|
60
|
+
- CMD-SHELL
|
|
61
|
+
- >-
|
|
62
|
+
asterisk -rx 'ari show status' | grep -q Enabled &&
|
|
63
|
+
asterisk -rx 'module show like chan_audiosocket' | grep -q Running
|
|
64
|
+
interval: 1s
|
|
65
|
+
retries: 30
|
|
66
|
+
|
|
67
|
+
engine:
|
|
68
|
+
build:
|
|
69
|
+
context: https://github.com/hkjarral/AVA-AI-Voice-Agent-for-Asterisk.git#ba52df92bc1cad2c1f277034e1423b80c8f50be4
|
|
70
|
+
# upstream's Dockerfile, with the base image and every installed package pinned
|
|
71
|
+
# (upstream pins some of its direct requirements)
|
|
72
|
+
additional_contexts: { pins: ./targets/ava }
|
|
73
|
+
dockerfile_inline: |
|
|
74
|
+
FROM python:3.11-slim-bookworm@sha256:0a310eeecf4e1f5a0743f9a6520c90c88d089c903ca5fd283f501e3a805f5f89 AS builder
|
|
75
|
+
WORKDIR /usr/src/app
|
|
76
|
+
RUN apt-get -o Acquire::Retries=5 update && apt-get install -y --no-install-recommends \
|
|
77
|
+
build-essential && rm -rf /var/lib/apt/lists/*
|
|
78
|
+
RUN python -m venv /opt/venv
|
|
79
|
+
ENV PATH="/opt/venv/bin:$$PATH"
|
|
80
|
+
COPY requirements.txt .
|
|
81
|
+
COPY --from=pins constraints.txt /tmp/constraints.txt
|
|
82
|
+
RUN pip install --no-cache-dir -r requirements.txt -c /tmp/constraints.txt
|
|
83
|
+
|
|
84
|
+
FROM python:3.11-slim-bookworm@sha256:0a310eeecf4e1f5a0743f9a6520c90c88d089c903ca5fd283f501e3a805f5f89
|
|
85
|
+
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
|
|
86
|
+
RUN apt-get -o Acquire::Retries=5 update \
|
|
87
|
+
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends tzdata \
|
|
88
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
89
|
+
WORKDIR /app
|
|
90
|
+
RUN groupadd -g 995 asterisk || true && useradd --create-home appuser \
|
|
91
|
+
&& usermod -aG 995 appuser
|
|
92
|
+
COPY --from=builder /opt/venv /opt/venv
|
|
93
|
+
COPY --chown=appuser:appuser src/ ./src
|
|
94
|
+
COPY --chown=appuser:appuser config/ ./config
|
|
95
|
+
COPY --chown=appuser:appuser main.py ./
|
|
96
|
+
RUN mkdir -p /app/logs /app/data && chown appuser:appuser /app/logs /app/data
|
|
97
|
+
ENV PATH="/opt/venv/bin:$$PATH"
|
|
98
|
+
USER appuser
|
|
99
|
+
CMD ["python", "main.py"]
|
|
100
|
+
image: ${RTPBENCH_REGISTRY:?}/rtpbench-ava:ba52df9${RTPBENCH_BUILD_ENGINE:-}
|
|
101
|
+
network_mode: service:target
|
|
102
|
+
depends_on:
|
|
103
|
+
asterisk: { condition: service_healthy }
|
|
104
|
+
volumes: ["./targets/ava/ai-agent.yaml:/app/config/ai-agent.yaml:ro"]
|
|
105
|
+
environment:
|
|
106
|
+
ASTERISK_HOST: 127.0.0.1
|
|
107
|
+
ASTERISK_ARI_PORT: "8088"
|
|
108
|
+
ASTERISK_ARI_USERNAME: asterisk
|
|
109
|
+
ASTERISK_ARI_PASSWORD: asterisk
|
|
110
|
+
OPENAI_API_KEY: ${OPENAI_API_KEY:?set OPENAI_API_KEY}
|
|
111
|
+
OPENAI_BASE_URL: ${OPENAI_BASE_URL:-wss://api.openai.com/v1/realtime}
|
|
112
|
+
OPENAI_MODEL: ${OPENAI_MODEL:-gpt-realtime-2.1}
|
|
113
|
+
# the golden config's egress pacer: on as shipped, false to measure what it costs
|
|
114
|
+
AVA_EGRESS_PACER: ${AVA_EGRESS_PACER:-true}
|
|
115
|
+
# how Asterisk hands AVA the call audio: AudioSocket (TCP) as shipped, externalmedia (RTP)
|
|
116
|
+
# or websocket (chan_websocket)
|
|
117
|
+
AVA_TRANSPORT: ${AVA_TRANSPORT:-audiosocket}
|
|
118
|
+
# websocket's listener password, as in websocket_client.conf
|
|
119
|
+
ASTERISK_MEDIA_WS_PASSWORD: asterisk
|
|
120
|
+
LOG_LEVEL: debug
|
|
121
|
+
# ready once the engine serves its health endpoint
|
|
122
|
+
healthcheck:
|
|
123
|
+
test: python -c 'import urllib.request as u; u.urlopen("http://127.0.0.1:15000/health")'
|
|
124
|
+
interval: 1s
|
|
125
|
+
retries: 60
|
|
126
|
+
|
|
127
|
+
networks:
|
|
128
|
+
egress:
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
aiohappyeyeballs==2.7.1
|
|
2
|
+
aiohttp==3.14.3
|
|
3
|
+
aiosignal==1.4.0
|
|
4
|
+
annotated-types==0.8.0
|
|
5
|
+
anyio==4.15.1
|
|
6
|
+
ari==0.1.3
|
|
7
|
+
attrs==26.1.0
|
|
8
|
+
azure-cognitiveservices-speech==1.52.0
|
|
9
|
+
azure-core==1.41.0
|
|
10
|
+
certifi==2026.10.18
|
|
11
|
+
cffi==2.1.1
|
|
12
|
+
charset-normalizer==3.5.2
|
|
13
|
+
click==8.5.0
|
|
14
|
+
cryptography==50.0.2
|
|
15
|
+
dnspython==2.8.0
|
|
16
|
+
frozenlist==1.8.0
|
|
17
|
+
google-api-core==2.42.0
|
|
18
|
+
google-api-python-client==2.201.0
|
|
19
|
+
google-auth==2.62.0
|
|
20
|
+
google-auth-httplib2==0.4.4
|
|
21
|
+
googleapis-common-protos==1.75.5
|
|
22
|
+
h11==0.16.0
|
|
23
|
+
httpcore2==2.14.0
|
|
24
|
+
httplib2==0.32.0
|
|
25
|
+
httpx2==2.14.0
|
|
26
|
+
idna==3.20
|
|
27
|
+
Jinja2==3.1.6
|
|
28
|
+
jiter==0.17.0
|
|
29
|
+
jsonschema==4.26.0
|
|
30
|
+
jsonschema-specifications==2025.9.1
|
|
31
|
+
MarkupSafe==3.0.4
|
|
32
|
+
mcp==2.3.0
|
|
33
|
+
mcp-types==2.3.0
|
|
34
|
+
msal==1.39.0
|
|
35
|
+
msgpack==1.2.1
|
|
36
|
+
multidict==6.9.1
|
|
37
|
+
numpy==2.4.6
|
|
38
|
+
openai==3.35.1
|
|
39
|
+
opentelemetry-api==1.45.1
|
|
40
|
+
portalocker==4.4.1
|
|
41
|
+
prometheus_client==0.24.1
|
|
42
|
+
propcache==0.5.4
|
|
43
|
+
proto-plus==1.29.0
|
|
44
|
+
protobuf==7.36.2
|
|
45
|
+
pyasn1==0.6.4
|
|
46
|
+
pyasn1_modules==0.4.2
|
|
47
|
+
pycparser==3.11
|
|
48
|
+
pydantic==2.14.1
|
|
49
|
+
pydantic-settings==2.14.2
|
|
50
|
+
pydantic_core==2.50.1
|
|
51
|
+
PyJWT==2.15.1
|
|
52
|
+
pyparsing==3.3.3
|
|
53
|
+
python-dotenv==1.2.4
|
|
54
|
+
python-multipart==0.0.32
|
|
55
|
+
PyYAML==6.0.3
|
|
56
|
+
referencing==0.37.0
|
|
57
|
+
requests==2.34.2
|
|
58
|
+
resend==2.50.0
|
|
59
|
+
rpds-py==2026.9.1
|
|
60
|
+
sniffio==1.3.1
|
|
61
|
+
sse-starlette==3.5.0
|
|
62
|
+
starlette==1.9.0
|
|
63
|
+
structlog==26.1.0
|
|
64
|
+
swaggerpy==0.2.1
|
|
65
|
+
tenacity==8.2.3
|
|
66
|
+
truststore==0.10.5
|
|
67
|
+
typing-inspection==0.4.4
|
|
68
|
+
typing_extensions==4.16.0
|
|
69
|
+
uritemplate==4.2.0
|
|
70
|
+
urllib3==2.8.0
|
|
71
|
+
uvicorn==0.54.0
|
|
72
|
+
webrtcvad==2.0.10
|
|
73
|
+
websocket-client==1.9.2
|
|
74
|
+
websockets==16.1.1
|
|
75
|
+
yarl==1.25.1
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
; the caller dials `agent`; AVA's ARI app answers and runs the call
|
|
2
|
+
[from-caller]
|
|
3
|
+
exten => agent,1,Stasis(asterisk-ai-voice-agent)
|
|
4
|
+
same => n,Hangup()
|
|
5
|
+
|
|
6
|
+
; AVA's blind_transfer continues the answered call here: Transfer() sends a REFER, waits for
|
|
7
|
+
; its outcome and leaves the call up, so hang up after it either way (AVA's session has ended)
|
|
8
|
+
[transfer]
|
|
9
|
+
exten => operator,1,Transfer(PJSIP/sip:operator@operator)
|
|
10
|
+
same => n,Hangup()
|