pipecat-backchannel 0.2.0__tar.gz

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 (30) hide show
  1. pipecat_backchannel-0.2.0/.gitignore +10 -0
  2. pipecat_backchannel-0.2.0/CLAUDE.md +86 -0
  3. pipecat_backchannel-0.2.0/LICENSE +24 -0
  4. pipecat_backchannel-0.2.0/PKG-INFO +215 -0
  5. pipecat_backchannel-0.2.0/README.md +194 -0
  6. pipecat_backchannel-0.2.0/examples/.env.template +14 -0
  7. pipecat_backchannel-0.2.0/examples/bot.py +150 -0
  8. pipecat_backchannel-0.2.0/examples/demo-script.md +76 -0
  9. pipecat_backchannel-0.2.0/examples/voice-test-script.md +194 -0
  10. pipecat_backchannel-0.2.0/pyproject.toml +70 -0
  11. pipecat_backchannel-0.2.0/src/pipecat_backchannel/__init__.py +49 -0
  12. pipecat_backchannel-0.2.0/src/pipecat_backchannel/cache.py +114 -0
  13. pipecat_backchannel-0.2.0/src/pipecat_backchannel/clips.py +288 -0
  14. pipecat_backchannel-0.2.0/src/pipecat_backchannel/frames.py +17 -0
  15. pipecat_backchannel-0.2.0/src/pipecat_backchannel/library.py +165 -0
  16. pipecat_backchannel-0.2.0/src/pipecat_backchannel/placement.py +112 -0
  17. pipecat_backchannel-0.2.0/src/pipecat_backchannel/player.py +110 -0
  18. pipecat_backchannel-0.2.0/src/pipecat_backchannel/plugin.py +160 -0
  19. pipecat_backchannel-0.2.0/src/pipecat_backchannel/prewarm.py +108 -0
  20. pipecat_backchannel-0.2.0/src/pipecat_backchannel/processor.py +324 -0
  21. pipecat_backchannel-0.2.0/src/pipecat_backchannel/py.typed +0 -0
  22. pipecat_backchannel-0.2.0/src/pipecat_backchannel/recorder.py +258 -0
  23. pipecat_backchannel-0.2.0/src/pipecat_backchannel/synth.py +45 -0
  24. pipecat_backchannel-0.2.0/src/pipecat_backchannel/turn.py +89 -0
  25. pipecat_backchannel-0.2.0/tests/conftest.py +251 -0
  26. pipecat_backchannel-0.2.0/tests/test_clips.py +296 -0
  27. pipecat_backchannel-0.2.0/tests/test_gate.py +516 -0
  28. pipecat_backchannel-0.2.0/tests/test_plugin.py +158 -0
  29. pipecat_backchannel-0.2.0/tests/test_startup.py +306 -0
  30. pipecat_backchannel-0.2.0/uv.lock +3171 -0
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .env
10
+ .clip_cache/
@@ -0,0 +1,86 @@
1
+ # pipecat-backchannel
2
+
3
+ ## The idea
4
+
5
+ Human conversation runs on two channels at once. One person holds the floor and talks. The other, without ever taking the floor, keeps feeding back short signals — "mhm", "yeah", "right". These are **backchannels**, and they mean *keep going, I'm with you*.
6
+
7
+ They are not turns. The speaker doesn't stop for them, doesn't answer them, and doesn't remember them as things the listener said. They're a parallel stream of evidence that someone is still on the other end.
8
+
9
+ Voice agents have no such channel. They are silent for as long as you speak, then reply once you stop. From the user's side that silence is indistinguishable from a hung connection, so they compensate: trailing off, repeating themselves, cutting a thought short to check the agent is still there. The conversation stops feeling live.
10
+
11
+ This library adds that missing channel to a voice agent, and nothing else. It is not a feature of the agent's intelligence — the agent never knows it happened. It's a thin behavioral layer that makes the agent *sound* like it's listening while it listens.
12
+
13
+ ## Why it's shaped the way it is
14
+
15
+ The problem decomposes into decisions that are genuinely independent, and the architecture's whole job is to keep them that way:
16
+
17
+ - **When to signal.** A timing decision, made from audio alone. This is the hard part and the only one that can ruin the experience.
18
+ - **What to signal.** Which flavor of acknowledgment fits the moment.
19
+ - **Where the sound comes from.** Synthesis, recording, a vendor — an infrastructure concern.
20
+ - **How it reaches the user.** Turning a decision into audio in the pipeline.
21
+
22
+ Collapsing any two of these together is the mistake this design exists to prevent. They change for different reasons, at different rates, under different constraints.
23
+
24
+ Three properties fall out of the domain and are not negotiable:
25
+
26
+ **Timing is a hard deadline, not a goal.** A backchannel is only correct inside a narrow window. Slightly late is worse than never — an acknowledgment that lands after the moment has passed sounds like an interruption. Anything slow must therefore happen ahead of time, never in the moment. Work is precomputed at startup so the live decision is pure and instant.
27
+
28
+ **The decisions differ wildly in stakes.** Getting the timing wrong makes the agent talk over people. Getting the flavor wrong makes it sound very slightly off. These must not be coupled, and the cheap, sloppy decision must never be allowed to influence the expensive, careful one.
29
+
30
+ **Listening is not speaking.** A backchannel must leave no trace in the agent's memory of the conversation. The moment it's recorded as something the agent said, it has become a turn, and the whole premise is gone.
31
+
32
+ Read those three as the acceptance criteria for any change here. A change that's elegant but violates one of them is wrong.
33
+
34
+ ---
35
+
36
+ # Engineering principles
37
+
38
+ ## Dependency inversion — the one that matters most
39
+
40
+ **Depend on abstractions, never on concretions.** High-level policy must not know which low-level implementation serves it. If a module names a vendor, a library, or a driver, it is welded to it.
41
+
42
+ **Inject dependencies; never construct them inside.** A component that builds its own collaborators has silently made them mandatory and untestable. Pass them in, with a sensible default where there's an obvious one.
43
+
44
+ This is what buys everything else:
45
+
46
+ - **Swappability.** A new provider is a new implementation, not an edit.
47
+ - **Testability.** Inject a fake and the test is fast, offline, deterministic. If something is hard to test, that's the design telling you it's too coupled — fix the coupling, don't mock harder.
48
+ - **Blast radius.** Concrete details stay at the edges. A change out there doesn't reach the core.
49
+
50
+ The abstraction must be defined by the **consumer's** need, not the implementation's shape. An interface that mirrors one vendor's API is that vendor wearing a hat — the second implementation will not fit it.
51
+
52
+ ## Simple first
53
+
54
+ Write the obvious thing. Complexity should arrive because something demanded it, not because you predicted it would. Speculative generality is paid for now, for a benefit that usually never comes.
55
+
56
+ You cannot guess the right abstraction from one use case. Wait for the second. Duplication is cheaper than the wrong abstraction — it's easy to merge later, while a bad abstraction is hard to remove once everything depends on it.
57
+
58
+ ## Architecturally correct first
59
+
60
+ Simple is not sloppy. Get the **seams** right early — where things plug together is cheap to place now and expensive to move later. Everything else can be crude and improved in place.
61
+
62
+ A wrong abstraction is worse than none.
63
+
64
+ ## SOLID
65
+
66
+ - **S** — one reason to change. If describing a component needs "and", split it.
67
+ - **O** — extend by adding, not by editing. New behavior shouldn't mean touching working code.
68
+ - **L** — a subtype must be usable anywhere its base is, without surprises.
69
+ - **I** — many small interfaces beat one fat one. Don't force a caller to depend on methods it ignores.
70
+ - **D** — see above.
71
+
72
+ ## DRY
73
+
74
+ Deduplicate **knowledge, not text.** Two pieces of code that look alike but change for different reasons are not duplicates — merging them couples them forever. One fact should have one home; that's the point.
75
+
76
+ ## Abstraction
77
+
78
+ An abstraction earns its place by hiding something the caller genuinely shouldn't know. If it only forwards calls, delete it — it's a layer, not an abstraction.
79
+
80
+ Name things for what they mean, not how they work. The name is the abstraction; if you can't name it clearly, the boundary is wrong.
81
+
82
+ ## KISS
83
+
84
+ The reader is the constraint. Code is read far more than written, and the next reader has none of your context. Clever costs more than it saves.
85
+
86
+ Prefer boring, obvious, explicit. Fewer moving parts, fewer states, fewer ways to hold it wrong.
@@ -0,0 +1,24 @@
1
+ BSD 2-Clause License
2
+
3
+ Copyright (c) 2026 Taras Maister
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,215 @@
1
+ Metadata-Version: 2.4
2
+ Name: pipecat-backchannel
3
+ Version: 0.2.0
4
+ Summary: Make voice AI conversations feel live — Pipecat agents that listen back with natural "mhm"s and "yeah"s while the user is still talking.
5
+ Project-URL: Homepage, https://github.com/maisterr/pipecat-backchannel
6
+ Project-URL: Repository, https://github.com/maisterr/pipecat-backchannel
7
+ Project-URL: Issues, https://github.com/maisterr/pipecat-backchannel/issues
8
+ Author: Taras Maister
9
+ License-Expression: BSD-2-Clause
10
+ License-File: LICENSE
11
+ Keywords: backchannel,conversational-ai,pipecat,turn-taking,voice-ai
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
18
+ Requires-Python: >=3.11
19
+ Requires-Dist: pipecat-ai<2,>=1.6.0
20
+ Description-Content-Type: text/markdown
21
+
22
+ <div align="center">
23
+ <a href="https://github.com/pipecat-ai/pipecat">
24
+ <img alt="Pipecat" width="220px" height="auto" src="https://raw.githubusercontent.com/pipecat-ai/pipecat/main/pipecat.png">
25
+ </a>
26
+ <p><em>A community integration for <a href="https://github.com/pipecat-ai/pipecat">Pipecat</a></em></p>
27
+ </div>
28
+
29
+ # pipecat-backchannel
30
+
31
+ [![PyPI](https://img.shields.io/pypi/v/pipecat-backchannel)](https://pypi.org/project/pipecat-backchannel)
32
+ [![Python](https://img.shields.io/pypi/pyversions/pipecat-backchannel)](https://pypi.org/project/pipecat-backchannel)
33
+ [![License](https://img.shields.io/badge/license-BSD--2--Clause-blue)](LICENSE)
34
+ [![Pipecat](https://img.shields.io/badge/Pipecat-1.6.0-brightgreen)](https://github.com/pipecat-ai/pipecat)
35
+
36
+ Your Pipecat agent listens back: quiet "mhm"s and "yeah"s while the user is
37
+ still talking.
38
+
39
+ ## Why
40
+
41
+ Listen to two people on a phone call. While one tells a story, the other keeps
42
+ feeding back small signals: "mhm", "yeah". Linguists call these
43
+ **backchannels**. They mean _keep going, I'm with you_, and the speaker talks
44
+ straight through them.
45
+
46
+ Talk to a voice agent and you get silence until you finish. Silence on a call
47
+ reads as a dropped connection, so you hesitate, trail off, or ask "hello?".
48
+
49
+ This library adds the listening sounds. The agent keeps the floor with you the
50
+ whole time: a backchannel takes no turn and leaves no trace in your LLM
51
+ context.
52
+
53
+ ## Install
54
+
55
+ ```bash
56
+ uv add pipecat-backchannel
57
+ ```
58
+
59
+ ## Usage
60
+
61
+ ```python
62
+ from pipecat_backchannel import Backchannel
63
+
64
+ backchannel = Backchannel()
65
+
66
+ pipeline = Pipeline(backchannel([
67
+ transport.input(),
68
+ stt,
69
+ context_aggregator.user(),
70
+ llm,
71
+ tts,
72
+ transport.output(),
73
+ context_aggregator.assistant(),
74
+ ]))
75
+ ```
76
+
77
+ One wrapper call is the whole setup. The default configuration works without
78
+ an API key, a voice ID, or a sample rate, and places every processor for you.
79
+
80
+ The first run records the clips through **your own TTS**, so they come out in
81
+ your bot's voice, and caches them in `.clip_cache/`. Every later run starts
82
+ from the cache. Delete that directory after changing voice.
83
+
84
+ ## Record before the first call
85
+
86
+ The first session waits a few seconds while the recorder fills the clip cache.
87
+ Move that wait to app startup if you want to avoid delay for first caller:
88
+
89
+ ```python
90
+ backchannel = Backchannel() # one per process
91
+
92
+ async def run_bot(...):
93
+ pipeline = Pipeline(backchannel([... tts ...]))
94
+
95
+ if __name__ == "__main__":
96
+ asyncio.run(backchannel.prewarm(tts=make_tts())) # before the server opens
97
+ main()
98
+ ```
99
+
100
+ ## Tuning
101
+
102
+ ```python
103
+ Backchannel(
104
+ volume=0.6, # how loud, relative to the recording
105
+ params=BackchannelParams(
106
+ fire_probability=0.8, # chance of speaking up at a good moment
107
+ cooldown_s=2.5, # minimum gap between backchannels
108
+ min_speech_before_eligible_s=0.7, # ignore pauses right after a turn starts
109
+ ),
110
+ )
111
+ ```
112
+
113
+ `fire_probability` and `cooldown_s` set how present the listener feels. Raise
114
+ the first and lower the second for a more active one. Keep some slack: a bot
115
+ that reacts to each eligible pause sounds mechanical, because human listeners
116
+ skip some of them.
117
+
118
+ `volume` sits below 1.0 by default so clips play under the user's voice.
119
+
120
+ Set `loguru` to `DEBUG` to see each fire/skip decision and its reason.
121
+
122
+ ### Going quiet mid-session
123
+
124
+ Some stretches of a call want a silent listener: the user dictating a card
125
+ number, or the bot walking through a form where each pause belongs to a
126
+ field, and an "mhm" would read as confirmation.
127
+
128
+ The component that decides when to fire is the `BackchannelProcessor`, one
129
+ per pipeline. `backchannel([...])` returns your processor list with it
130
+ inserted, so keep that list, build the `Pipeline` from it, and pull the gate
131
+ out of it:
132
+
133
+ ```python
134
+ from pipecat_backchannel.processor import BackchannelProcessor
135
+
136
+ processors = backchannel([transport.input(), stt, llm, tts, transport.output()])
137
+ pipeline = Pipeline(processors)
138
+
139
+ gate = next(p for p in processors if isinstance(p, BackchannelProcessor))
140
+ ```
141
+
142
+ Then flip it from any event handler, as many times as the call needs:
143
+
144
+ ```python
145
+ gate.enabled = False # stop firing; every frame still passes through
146
+ ...
147
+ gate.enabled = True # resume
148
+ ```
149
+
150
+ `enabled` is a plain attribute, safe to set at any moment. While it is
151
+ `False` the processor keeps passing audio through and keeps tracking speech,
152
+ so nothing downstream notices and re-enabling picks up mid-turn. It fires no
153
+ clips and runs no end-of-turn classification in that state. The pipeline, the
154
+ LLM context, and the other sessions' gates stay untouched.
155
+
156
+ ## Custom clips
157
+
158
+ The library groups clips by conversational function: continuer, agreement,
159
+ hesitation, surprise.
160
+
161
+ ```python
162
+ Backchannel(clip_groups={"continue": ["Mhm.", "Right."], "affirm": ["Yeah.", "Yep."]})
163
+ ```
164
+
165
+ Two rules for a custom inventory:
166
+
167
+ - **Give each group at least two clips.** A one-clip group repeats itself.
168
+ - **Get variety from punctuation, and keep the vocabulary small.** Real
169
+ backchannels draw on a handful of sounds: _uh-huh, yeah, mm-hmm, right,
170
+ okay, oh, huh, hm_. A written phrase like "absolutely" reads fine on the
171
+ page, then sounds like an answer when spoken over someone mid-sentence.
172
+ Punctuation is the handle your TTS gives you: `"Mhm."` falls, `"Mhm,"` stays
173
+ up, `"Hm..."` draws out.
174
+
175
+ Pass `synthesizer=` to use a different voice, or `cache=` to supply your own
176
+ recorded `.wav` files. See `pipecat_backchannel.synth` and `.cache`.
177
+
178
+ ## Run the example
179
+
180
+ ```bash
181
+ cp examples/.env.template examples/.env # DEEPGRAM_API_KEY, CARTESIA_API_KEY, CEREBRAS_API_KEY
182
+ uv run examples/bot.py
183
+ ```
184
+
185
+ Open `http://localhost:7860/client`, talk in mid-length sentences, and pause
186
+ mid-thought. A quiet "mhm" lands on clause-complete pauses. Word-search pauses
187
+ ("we need the, uh...") and finished questions get silence, and the questions
188
+ get a real reply.
189
+
190
+ ## Limitations
191
+
192
+ - **Pauses only.** Humans backchannel during speech too, cued by pitch. This
193
+ library reacts to pauses. The roadmap covers the rest.
194
+ - **About 2× inference while the user speaks.** It runs a second VAD and
195
+ turn-detector on purpose: the pipeline's copies are tuned for turn-taking,
196
+ and pause timing needs the opposite settings. A handful of concurrent
197
+ sessions runs fine; measure before a large deployment.
198
+ - **English only.** The clips and the regexes are English. The underlying
199
+ models handle other languages.
200
+
201
+ ## Roadmap
202
+
203
+ - [ ] **Fire during speech.** Real listeners backchannel mid-utterance, cued
204
+ by pitch and rhythm rather than silence. That takes Voice Activity
205
+ Projection; [MaAI](https://github.com/MaAI-Kyoto/MaAI) ships a usable
206
+ model. It reads a different signal, so it slots in beside the pause
207
+ gate rather than replacing it, and it is the largest quality jump left.
208
+ - [ ] **Match the speaker's prosody.** The selector picks a clip at random
209
+ within its group. Picking the one whose pitch and energy fit the last
210
+ utterance adds no runtime cost, because the clips already sit on disk.
211
+
212
+ ## License
213
+
214
+ BSD 2-Clause © Taras Maister, matching Pipecat's own license. Not affiliated
215
+ with Daily or the Pipecat core team.
@@ -0,0 +1,194 @@
1
+ <div align="center">
2
+ <a href="https://github.com/pipecat-ai/pipecat">
3
+ <img alt="Pipecat" width="220px" height="auto" src="https://raw.githubusercontent.com/pipecat-ai/pipecat/main/pipecat.png">
4
+ </a>
5
+ <p><em>A community integration for <a href="https://github.com/pipecat-ai/pipecat">Pipecat</a></em></p>
6
+ </div>
7
+
8
+ # pipecat-backchannel
9
+
10
+ [![PyPI](https://img.shields.io/pypi/v/pipecat-backchannel)](https://pypi.org/project/pipecat-backchannel)
11
+ [![Python](https://img.shields.io/pypi/pyversions/pipecat-backchannel)](https://pypi.org/project/pipecat-backchannel)
12
+ [![License](https://img.shields.io/badge/license-BSD--2--Clause-blue)](LICENSE)
13
+ [![Pipecat](https://img.shields.io/badge/Pipecat-1.6.0-brightgreen)](https://github.com/pipecat-ai/pipecat)
14
+
15
+ Your Pipecat agent listens back: quiet "mhm"s and "yeah"s while the user is
16
+ still talking.
17
+
18
+ ## Why
19
+
20
+ Listen to two people on a phone call. While one tells a story, the other keeps
21
+ feeding back small signals: "mhm", "yeah". Linguists call these
22
+ **backchannels**. They mean _keep going, I'm with you_, and the speaker talks
23
+ straight through them.
24
+
25
+ Talk to a voice agent and you get silence until you finish. Silence on a call
26
+ reads as a dropped connection, so you hesitate, trail off, or ask "hello?".
27
+
28
+ This library adds the listening sounds. The agent keeps the floor with you the
29
+ whole time: a backchannel takes no turn and leaves no trace in your LLM
30
+ context.
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ uv add pipecat-backchannel
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ```python
41
+ from pipecat_backchannel import Backchannel
42
+
43
+ backchannel = Backchannel()
44
+
45
+ pipeline = Pipeline(backchannel([
46
+ transport.input(),
47
+ stt,
48
+ context_aggregator.user(),
49
+ llm,
50
+ tts,
51
+ transport.output(),
52
+ context_aggregator.assistant(),
53
+ ]))
54
+ ```
55
+
56
+ One wrapper call is the whole setup. The default configuration works without
57
+ an API key, a voice ID, or a sample rate, and places every processor for you.
58
+
59
+ The first run records the clips through **your own TTS**, so they come out in
60
+ your bot's voice, and caches them in `.clip_cache/`. Every later run starts
61
+ from the cache. Delete that directory after changing voice.
62
+
63
+ ## Record before the first call
64
+
65
+ The first session waits a few seconds while the recorder fills the clip cache.
66
+ Move that wait to app startup if you want to avoid delay for first caller:
67
+
68
+ ```python
69
+ backchannel = Backchannel() # one per process
70
+
71
+ async def run_bot(...):
72
+ pipeline = Pipeline(backchannel([... tts ...]))
73
+
74
+ if __name__ == "__main__":
75
+ asyncio.run(backchannel.prewarm(tts=make_tts())) # before the server opens
76
+ main()
77
+ ```
78
+
79
+ ## Tuning
80
+
81
+ ```python
82
+ Backchannel(
83
+ volume=0.6, # how loud, relative to the recording
84
+ params=BackchannelParams(
85
+ fire_probability=0.8, # chance of speaking up at a good moment
86
+ cooldown_s=2.5, # minimum gap between backchannels
87
+ min_speech_before_eligible_s=0.7, # ignore pauses right after a turn starts
88
+ ),
89
+ )
90
+ ```
91
+
92
+ `fire_probability` and `cooldown_s` set how present the listener feels. Raise
93
+ the first and lower the second for a more active one. Keep some slack: a bot
94
+ that reacts to each eligible pause sounds mechanical, because human listeners
95
+ skip some of them.
96
+
97
+ `volume` sits below 1.0 by default so clips play under the user's voice.
98
+
99
+ Set `loguru` to `DEBUG` to see each fire/skip decision and its reason.
100
+
101
+ ### Going quiet mid-session
102
+
103
+ Some stretches of a call want a silent listener: the user dictating a card
104
+ number, or the bot walking through a form where each pause belongs to a
105
+ field, and an "mhm" would read as confirmation.
106
+
107
+ The component that decides when to fire is the `BackchannelProcessor`, one
108
+ per pipeline. `backchannel([...])` returns your processor list with it
109
+ inserted, so keep that list, build the `Pipeline` from it, and pull the gate
110
+ out of it:
111
+
112
+ ```python
113
+ from pipecat_backchannel.processor import BackchannelProcessor
114
+
115
+ processors = backchannel([transport.input(), stt, llm, tts, transport.output()])
116
+ pipeline = Pipeline(processors)
117
+
118
+ gate = next(p for p in processors if isinstance(p, BackchannelProcessor))
119
+ ```
120
+
121
+ Then flip it from any event handler, as many times as the call needs:
122
+
123
+ ```python
124
+ gate.enabled = False # stop firing; every frame still passes through
125
+ ...
126
+ gate.enabled = True # resume
127
+ ```
128
+
129
+ `enabled` is a plain attribute, safe to set at any moment. While it is
130
+ `False` the processor keeps passing audio through and keeps tracking speech,
131
+ so nothing downstream notices and re-enabling picks up mid-turn. It fires no
132
+ clips and runs no end-of-turn classification in that state. The pipeline, the
133
+ LLM context, and the other sessions' gates stay untouched.
134
+
135
+ ## Custom clips
136
+
137
+ The library groups clips by conversational function: continuer, agreement,
138
+ hesitation, surprise.
139
+
140
+ ```python
141
+ Backchannel(clip_groups={"continue": ["Mhm.", "Right."], "affirm": ["Yeah.", "Yep."]})
142
+ ```
143
+
144
+ Two rules for a custom inventory:
145
+
146
+ - **Give each group at least two clips.** A one-clip group repeats itself.
147
+ - **Get variety from punctuation, and keep the vocabulary small.** Real
148
+ backchannels draw on a handful of sounds: _uh-huh, yeah, mm-hmm, right,
149
+ okay, oh, huh, hm_. A written phrase like "absolutely" reads fine on the
150
+ page, then sounds like an answer when spoken over someone mid-sentence.
151
+ Punctuation is the handle your TTS gives you: `"Mhm."` falls, `"Mhm,"` stays
152
+ up, `"Hm..."` draws out.
153
+
154
+ Pass `synthesizer=` to use a different voice, or `cache=` to supply your own
155
+ recorded `.wav` files. See `pipecat_backchannel.synth` and `.cache`.
156
+
157
+ ## Run the example
158
+
159
+ ```bash
160
+ cp examples/.env.template examples/.env # DEEPGRAM_API_KEY, CARTESIA_API_KEY, CEREBRAS_API_KEY
161
+ uv run examples/bot.py
162
+ ```
163
+
164
+ Open `http://localhost:7860/client`, talk in mid-length sentences, and pause
165
+ mid-thought. A quiet "mhm" lands on clause-complete pauses. Word-search pauses
166
+ ("we need the, uh...") and finished questions get silence, and the questions
167
+ get a real reply.
168
+
169
+ ## Limitations
170
+
171
+ - **Pauses only.** Humans backchannel during speech too, cued by pitch. This
172
+ library reacts to pauses. The roadmap covers the rest.
173
+ - **About 2× inference while the user speaks.** It runs a second VAD and
174
+ turn-detector on purpose: the pipeline's copies are tuned for turn-taking,
175
+ and pause timing needs the opposite settings. A handful of concurrent
176
+ sessions runs fine; measure before a large deployment.
177
+ - **English only.** The clips and the regexes are English. The underlying
178
+ models handle other languages.
179
+
180
+ ## Roadmap
181
+
182
+ - [ ] **Fire during speech.** Real listeners backchannel mid-utterance, cued
183
+ by pitch and rhythm rather than silence. That takes Voice Activity
184
+ Projection; [MaAI](https://github.com/MaAI-Kyoto/MaAI) ships a usable
185
+ model. It reads a different signal, so it slots in beside the pause
186
+ gate rather than replacing it, and it is the largest quality jump left.
187
+ - [ ] **Match the speaker's prosody.** The selector picks a clip at random
188
+ within its group. Picking the one whose pitch and energy fit the last
189
+ utterance adds no runtime cost, because the clips already sit on disk.
190
+
191
+ ## License
192
+
193
+ BSD 2-Clause © Taras Maister, matching Pipecat's own license. Not affiliated
194
+ with Daily or the Pipecat core team.
@@ -0,0 +1,14 @@
1
+ DEEPGRAM_API_KEY=your_deepgram_api_key_here
2
+ CARTESIA_API_KEY=your_cartesia_api_key_here
3
+ CARTESIA_VOICE_ID=71a7ad14-091c-4e8e-a314-022ece01c121
4
+ CEREBRAS_API_KEY=your_cerebras_api_key_here
5
+ CEREBRAS_MODEL=gemma-4-31b
6
+
7
+ # Daily WebRTC room (leave unset to run in local "webrtc" test mode instead)
8
+ DAILY_API_KEY=your_daily_api_key_here
9
+
10
+ # Main-pipeline VAD start_secs (barge-in/interruption sensitivity). Higher =
11
+ # fewer spurious interruptions from brief sounds, slower to react to real
12
+ # barge-in. Independent of backchannel pause timing, which BackchannelProcessor
13
+ # runs its own VAD for (BackchannelParams.vad_start_secs, default 0.2).
14
+ MAIN_VAD_START_SECS=0.4