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.
Files changed (144) hide show
  1. rtpbench/.dockerignore +4 -0
  2. rtpbench/Dockerfile +12 -0
  3. rtpbench/__init__.py +6 -0
  4. rtpbench/__main__.py +3 -0
  5. rtpbench/agent.py +746 -0
  6. rtpbench/analyze.py +520 -0
  7. rtpbench/bargein.py +272 -0
  8. rtpbench/bench.py +1069 -0
  9. rtpbench/caller.py +781 -0
  10. rtpbench/capture.py +33 -0
  11. rtpbench/carrier/compose.yaml +68 -0
  12. rtpbench/carrier/opensips.cfg +226 -0
  13. rtpbench/carrier/tls.cfg +25 -0
  14. rtpbench/carrier/tls.yaml +9 -0
  15. rtpbench/cases.py +1989 -0
  16. rtpbench/check.py +369 -0
  17. rtpbench/cli.py +616 -0
  18. rtpbench/clock.py +174 -0
  19. rtpbench/compose.yaml +55 -0
  20. rtpbench/corpus/README.md +55 -0
  21. rtpbench/corpus/cough-c1-0.wav +0 -0
  22. rtpbench/corpus/cough-c1-1.wav +0 -0
  23. rtpbench/corpus/cough-c2-0.wav +0 -0
  24. rtpbench/corpus/cough-c2-1.wav +0 -0
  25. rtpbench/corpus/cough-man-0.wav +0 -0
  26. rtpbench/corpus/cough-man-1.wav +0 -0
  27. rtpbench/corpus/cough-short-0.wav +0 -0
  28. rtpbench/corpus/cough-short-1.wav +0 -0
  29. rtpbench/corpus/cough-woman_three_times-0.wav +0 -0
  30. rtpbench/corpus/human-en-dr-0.wav +0 -0
  31. rtpbench/corpus/human-en-dr-1.wav +0 -0
  32. rtpbench/corpus/human-en-sc-0.wav +0 -0
  33. rtpbench/corpus/human-en-sc-1.wav +0 -0
  34. rtpbench/corpus/human-en-sc-2.wav +0 -0
  35. rtpbench/corpus/human-es-f012-0.wav +0 -0
  36. rtpbench/corpus/human-es-f012-1.wav +0 -0
  37. rtpbench/corpus/human-es-f012-2.wav +0 -0
  38. rtpbench/corpus/human-es-f016-0.wav +0 -0
  39. rtpbench/corpus/human-es-f016-1.wav +0 -0
  40. rtpbench/corpus/human-es-f016-2.wav +0 -0
  41. rtpbench/corpus/lines/en/interrupt-00.wav +0 -0
  42. rtpbench/corpus/lines/en/interrupt-01.wav +0 -0
  43. rtpbench/corpus/lines/en/interrupt-02.wav +0 -0
  44. rtpbench/corpus/lines/en/interrupt-03.wav +0 -0
  45. rtpbench/corpus/lines/en/prompt-00.wav +0 -0
  46. rtpbench/corpus/lines/en/prompt-01.wav +0 -0
  47. rtpbench/corpus/lines/en/prompt-02.wav +0 -0
  48. rtpbench/corpus/lines/en/prompt-03.wav +0 -0
  49. rtpbench/corpus/lines/en/prompt-04.wav +0 -0
  50. rtpbench/corpus/lines/en/prompt-05.wav +0 -0
  51. rtpbench/corpus/lines/en/prompt-06.wav +0 -0
  52. rtpbench/corpus/lines/en/prompt-07.wav +0 -0
  53. rtpbench/corpus/lines/en/prompt-08.wav +0 -0
  54. rtpbench/corpus/lines/en/prompt-09.wav +0 -0
  55. rtpbench/corpus/lines/en/prompt.wav +0 -0
  56. rtpbench/corpus/lines/en/transfer.wav +0 -0
  57. rtpbench/corpus/lines/en/turn-00.wav +0 -0
  58. rtpbench/corpus/lines/en/turn-01.wav +0 -0
  59. rtpbench/corpus/lines/en/turn-02.wav +0 -0
  60. rtpbench/corpus/lines/en/turn-03.wav +0 -0
  61. rtpbench/corpus/lines/en/turn-04.wav +0 -0
  62. rtpbench/corpus/lines/en/turn-05.wav +0 -0
  63. rtpbench/corpus/lines/en/turn-06.wav +0 -0
  64. rtpbench/corpus/lines/en/turn-07.wav +0 -0
  65. rtpbench/corpus/lines/en/turn-08.wav +0 -0
  66. rtpbench/corpus/lines/en/turn-09.wav +0 -0
  67. rtpbench/corpus/lines/en/turn-10.wav +0 -0
  68. rtpbench/corpus/lines/en/turn-11.wav +0 -0
  69. rtpbench/corpus/tts-ald-bc.wav +0 -0
  70. rtpbench/corpus/tts-ald-long.wav +0 -0
  71. rtpbench/corpus/tts-ald-s03.wav +0 -0
  72. rtpbench/corpus/tts-ald-s07.wav +0 -0
  73. rtpbench/corpus/tts-ald-s11.wav +0 -0
  74. rtpbench/corpus/tts-cori-bc.wav +0 -0
  75. rtpbench/corpus/tts-cori-long.wav +0 -0
  76. rtpbench/corpus/tts-cori-s01.wav +0 -0
  77. rtpbench/corpus/tts-cori-s05.wav +0 -0
  78. rtpbench/corpus/tts-cori-s09.wav +0 -0
  79. rtpbench/corpus/tts-davefx-bc.wav +0 -0
  80. rtpbench/corpus/tts-davefx-long.wav +0 -0
  81. rtpbench/corpus/tts-davefx-s00.wav +0 -0
  82. rtpbench/corpus/tts-davefx-s04.wav +0 -0
  83. rtpbench/corpus/tts-davefx-s08.wav +0 -0
  84. rtpbench/corpus/tts-libritts0-bc.wav +0 -0
  85. rtpbench/corpus/tts-libritts0-long.wav +0 -0
  86. rtpbench/corpus/tts-libritts0-s02.wav +0 -0
  87. rtpbench/corpus/tts-libritts0-s06.wav +0 -0
  88. rtpbench/corpus/tts-libritts0-s10.wav +0 -0
  89. rtpbench/corpus/tts-libritts3-bc.wav +0 -0
  90. rtpbench/corpus/tts-libritts3-long.wav +0 -0
  91. rtpbench/corpus/tts-libritts3-s03.wav +0 -0
  92. rtpbench/corpus/tts-libritts3-s07.wav +0 -0
  93. rtpbench/corpus/tts-libritts3-s11.wav +0 -0
  94. rtpbench/corpus/tts-ljspeech-bc.wav +0 -0
  95. rtpbench/corpus/tts-ljspeech-long.wav +0 -0
  96. rtpbench/corpus/tts-ljspeech-s00.wav +0 -0
  97. rtpbench/corpus/tts-ljspeech-s04.wav +0 -0
  98. rtpbench/corpus/tts-ljspeech-s08.wav +0 -0
  99. rtpbench/corpus/tts-sharvardf-bc.wav +0 -0
  100. rtpbench/corpus/tts-sharvardf-long.wav +0 -0
  101. rtpbench/corpus/tts-sharvardf-s02.wav +0 -0
  102. rtpbench/corpus/tts-sharvardf-s06.wav +0 -0
  103. rtpbench/corpus/tts-sharvardf-s10.wav +0 -0
  104. rtpbench/corpus/tts-sharvardm-bc.wav +0 -0
  105. rtpbench/corpus/tts-sharvardm-long.wav +0 -0
  106. rtpbench/corpus/tts-sharvardm-s01.wav +0 -0
  107. rtpbench/corpus/tts-sharvardm-s05.wav +0 -0
  108. rtpbench/corpus/tts-sharvardm-s09.wav +0 -0
  109. rtpbench/detect.py +145 -0
  110. rtpbench/g711.py +82 -0
  111. rtpbench/mock/compose.yaml +18 -0
  112. rtpbench/pcap.py +106 -0
  113. rtpbench/progress.py +184 -0
  114. rtpbench/realtime.py +1015 -0
  115. rtpbench/report.py +450 -0
  116. rtpbench/rtp.py +166 -0
  117. rtpbench/sip.py +653 -0
  118. rtpbench/speech.py +100 -0
  119. rtpbench/speechcal.py +367 -0
  120. rtpbench/srtp.py +191 -0
  121. rtpbench/targets/aivc/aivc.ini +25 -0
  122. rtpbench/targets/aivc/compose.yaml +72 -0
  123. rtpbench/targets/aivc/constraints.txt +52 -0
  124. rtpbench/targets/aivc/opensips.cfg +45 -0
  125. rtpbench/targets/ava/ai-agent.yaml +275 -0
  126. rtpbench/targets/ava/ari.conf +7 -0
  127. rtpbench/targets/ava/compose.yaml +128 -0
  128. rtpbench/targets/ava/constraints.txt +75 -0
  129. rtpbench/targets/ava/extensions.conf +10 -0
  130. rtpbench/targets/ava/http.conf +4 -0
  131. rtpbench/targets/ava/pjsip.conf +19 -0
  132. rtpbench/targets/ava/websocket_client.conf +15 -0
  133. rtpbench/targets/livekit/agent.py +111 -0
  134. rtpbench/targets/livekit/compose.yaml +86 -0
  135. rtpbench/targets/livekit/constraints.txt +72 -0
  136. rtpbench/tls.py +284 -0
  137. rtpbench/uri/advertise.yaml +7 -0
  138. rtpbench/uri/network.yaml +14 -0
  139. rtpbench/voice.py +18 -0
  140. rtpbench-0.3.0.dist-info/METADATA +123 -0
  141. rtpbench-0.3.0.dist-info/RECORD +144 -0
  142. rtpbench-0.3.0.dist-info/WHEEL +4 -0
  143. rtpbench-0.3.0.dist-info/entry_points.txt +2 -0
  144. 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,7 @@
1
+ ; ARI for AVA's engine, which shares this network namespace (http.conf: loopback only)
2
+ [general]
3
+ enabled=yes
4
+ [asterisk]
5
+ type=user
6
+ read_only=no
7
+ password=asterisk
@@ -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()