pipecat-roark 0.1.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.
@@ -0,0 +1,16 @@
1
+ # ---------------------------------------------------------------------------
2
+ # Roark — required by RoarkObserver itself
3
+ # ---------------------------------------------------------------------------
4
+
5
+ # Roark API key — create one on the API keys page in your Roark project.
6
+ # This is the only Roark setting you need; the observer knows its own endpoints.
7
+ ROARK_API_KEY=rk_live_replace_me
8
+
9
+ # ---------------------------------------------------------------------------
10
+ # Services — only needed to run examples/bot.py (not by the library itself).
11
+ # Swap providers freely in your own pipeline; RoarkObserver is provider-agnostic.
12
+ # ---------------------------------------------------------------------------
13
+
14
+ DEEPGRAM_API_KEY=
15
+ OPENAI_API_KEY=
16
+ CARTESIA_API_KEY=
@@ -0,0 +1,31 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: ${{ matrix.python-version }}
20
+ - name: Install
21
+ run: |
22
+ python -m pip install --upgrade pip
23
+ # Tests that import pipecat-ai are guarded with pytest.importorskip,
24
+ # so installing the dev extras alone is enough for CI.
25
+ pip install -e ".[dev]"
26
+ - name: Lint
27
+ run: ruff check .
28
+ - name: Type check
29
+ run: mypy src/pipecat_roark
30
+ - name: Test
31
+ run: pytest -q
@@ -0,0 +1,93 @@
1
+ name: Release
2
+
3
+ # Auto-publish to PyPI on merge to main, but only when the version in
4
+ # pyproject.toml has been bumped. Merging changes that don't touch the
5
+ # version is a no-op (no duplicate-upload failures on PyPI).
6
+ on:
7
+ push:
8
+ branches: [main]
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ # 1. Decide whether this push is a release: did the version change?
15
+ check-version:
16
+ runs-on: ubuntu-latest
17
+ outputs:
18
+ changed: ${{ steps.check.outputs.changed }}
19
+ version: ${{ steps.check.outputs.version }}
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ with:
23
+ fetch-depth: 0 # need full history so we can see existing tags
24
+ - name: Check whether the version was bumped
25
+ id: check
26
+ run: |
27
+ version=$(grep -m1 -E '^version = ' pyproject.toml | sed -E 's/version = "(.*)"/\1/')
28
+ echo "version=$version" >> "$GITHUB_OUTPUT"
29
+ if git rev-parse "v$version" >/dev/null 2>&1; then
30
+ echo "Tag v$version already exists — nothing to publish."
31
+ echo "changed=false" >> "$GITHUB_OUTPUT"
32
+ else
33
+ echo "New version v$version detected — will publish."
34
+ echo "changed=true" >> "$GITHUB_OUTPUT"
35
+ fi
36
+
37
+ # 2. Gate the release on the full test/lint/type-check matrix passing.
38
+ test:
39
+ needs: check-version
40
+ if: needs.check-version.outputs.changed == 'true'
41
+ runs-on: ubuntu-latest
42
+ strategy:
43
+ fail-fast: false
44
+ matrix:
45
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
46
+ steps:
47
+ - uses: actions/checkout@v4
48
+ - uses: actions/setup-python@v5
49
+ with:
50
+ python-version: ${{ matrix.python-version }}
51
+ - name: Install
52
+ run: |
53
+ python -m pip install --upgrade pip
54
+ pip install -e ".[dev]"
55
+ - name: Lint
56
+ run: ruff check .
57
+ - name: Type check
58
+ run: mypy src/pipecat_roark
59
+ - name: Test
60
+ run: pytest -q
61
+
62
+ # 3. Build, publish to PyPI via Trusted Publishing, then tag + release on GitHub.
63
+ publish:
64
+ needs: [check-version, test]
65
+ if: needs.check-version.outputs.changed == 'true'
66
+ runs-on: ubuntu-latest
67
+ permissions:
68
+ # PyPI Trusted Publishing — no API token in repo secrets.
69
+ # See https://docs.pypi.org/trusted-publishers/
70
+ id-token: write
71
+ # Needed to push the version tag and create the GitHub release.
72
+ contents: write
73
+ steps:
74
+ - uses: actions/checkout@v4
75
+ - uses: actions/setup-python@v5
76
+ with:
77
+ python-version: "3.12"
78
+ - name: Build
79
+ run: |
80
+ python -m pip install --upgrade pip build
81
+ python -m build
82
+ - name: Publish to PyPI
83
+ uses: pypa/gh-action-pypi-publish@release/v1
84
+ - name: Tag and create GitHub release
85
+ env:
86
+ GH_TOKEN: ${{ github.token }}
87
+ run: |
88
+ version="${{ needs.check-version.outputs.version }}"
89
+ git tag "v$version"
90
+ git push origin "v$version"
91
+ gh release create "v$version" dist/* \
92
+ --title "v$version" \
93
+ --generate-notes
@@ -0,0 +1,27 @@
1
+ # Build / dist
2
+ build/
3
+ dist/
4
+ *.egg-info/
5
+ *.egg
6
+ __pycache__/
7
+ *.pyc
8
+ *.pyo
9
+ .pytest_cache/
10
+ .ruff_cache/
11
+ .mypy_cache/
12
+
13
+ # Virtualenvs
14
+ .venv/
15
+ venv/
16
+ env/
17
+
18
+ # Environment
19
+ .env
20
+ .env.local
21
+
22
+ # IDE
23
+ .vscode/
24
+ .idea/
25
+
26
+ # OS
27
+ .DS_Store
@@ -0,0 +1,64 @@
1
+ # Changelog
2
+
3
+ All notable changes to `pipecat-roark` are documented here.
4
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
5
+ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Changed
10
+
11
+ - **BREAKING:** Roark service endpoints are now built into the client. The
12
+ `ROARK_WEBHOOK_URL` and `ROARK_CHUNK_UPLOAD_URL_ENDPOINT` env vars are no
13
+ longer read or required — `ROARK_API_KEY` (or `api_key=`) is the only Roark
14
+ configuration. Remove those two vars from your environment / deployment
15
+ secrets; they are now ignored.
16
+
17
+ ## [0.1.0] - 2026-05-22
18
+
19
+ Initial public release. Drop-in `RoarkObserver` for Pipecat that ships call
20
+ lifecycle, transcripts, tool calls, and audio recordings to Roark. Compatible
21
+ with `pipecat-ai >= 0.0.40, < 1`; tested with `pipecat-ai` 0.0.108. Prepared
22
+ for submission to the
23
+ [Pipecat community-integrations](https://github.com/pipecat-ai/pipecat/blob/main/COMMUNITY_INTEGRATIONS.md)
24
+ listing.
25
+
26
+ ### Added
27
+
28
+ - `RoarkObserver` (`BaseObserver` subclass) capturing call lifecycle from
29
+ native Pipecat frames — `TranscriptionFrame`, `TTSTextFrame`,
30
+ `BotStoppedSpeakingFrame`, `InterruptionFrame`, `FunctionCallInProgressFrame`,
31
+ `FunctionCallResultFrame`, `EndFrame`, `CancelFrame`, `StopFrame`.
32
+ - Default `AudioBufferProcessor` auto-created with stereo channels and
33
+ ~256 KB chunks. Sample rate is adopted from the pipeline's `StartFrame` so
34
+ it tracks whatever the transport negotiated (8 kHz Twilio/Telnyx,
35
+ 16/24/48 kHz Daily/LiveKit). Exposed as `observer.audio_processor` for
36
+ power users; pass `audio_buffer_processor=` to override.
37
+ - Chunked audio upload via presigned S3 URLs requested per chunk from the
38
+ Roark chunk-upload endpoint; in-flight uploads are drained before
39
+ `call-ended` is posted.
40
+ - Frame deduplication by frame id (Pipecat invokes `on_push_frame` once per
41
+ processor-to-processor hop; without dedupe the same transcription would
42
+ repeat N times).
43
+ - `aflush(reason=...)` idempotent escape hatch for WebRTC transports that
44
+ tear down without pushing `EndFrame` (notably `SmallWebRTC`).
45
+ - OpenTelemetry correlation via shared `pipecat_call_id` ↔
46
+ `PipelineTask.conversation_id`.
47
+ - `examples/basic_observer.py` — minimal transport-agnostic wiring sketch.
48
+ - `examples/bot.py` — runnable foundational voice assistant
49
+ (Deepgram STT → OpenAI LLM → Cartesia TTS) using the canonical Pipecat
50
+ 0.0.108 runner pattern (`LLMContext` + `LLMContextAggregatorPair`,
51
+ `create_transport`, `on_client_connected` / `on_client_disconnected`).
52
+ Same file runs self-hosted (`--transport webrtc` / `--transport daily`)
53
+ and deploys to Pipecat Cloud unchanged.
54
+
55
+ ### Configuration
56
+
57
+ - `ROARK_WEBHOOK_URL` and `ROARK_CHUNK_UPLOAD_URL_ENDPOINT` env vars are
58
+ required at construction time — read directly from the environment, no
59
+ kwarg overrides.
60
+ - `api_key`, `agent_id` are required; `agent_name`, `agent_prompt`,
61
+ `pipecat_call_id`, `audio_buffer_processor` are optional.
62
+
63
+ [Unreleased]: https://github.com/roarkhq/pipecat-roark/compare/v0.1.0...HEAD
64
+ [0.1.0]: https://github.com/roarkhq/pipecat-roark/releases/tag/v0.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Roark, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,400 @@
1
+ Metadata-Version: 2.4
2
+ Name: pipecat-roark
3
+ Version: 0.1.0
4
+ Summary: Roark analytics observer for Pipecat — capture call lifecycle, transcripts, tool calls, and recordings from any Pipecat pipeline.
5
+ Project-URL: Homepage, https://roark.ai
6
+ Project-URL: Documentation, https://docs.roark.ai/integrations/pipecat
7
+ Project-URL: Repository, https://github.com/roarkhq/pipecat-roark
8
+ Project-URL: Issues, https://github.com/roarkhq/pipecat-roark/issues
9
+ Author-email: Roark <support@roark.ai>
10
+ License: MIT License
11
+
12
+ Copyright (c) 2026 Roark, Inc.
13
+
14
+ Permission is hereby granted, free of charge, to any person obtaining a copy
15
+ of this software and associated documentation files (the "Software"), to deal
16
+ in the Software without restriction, including without limitation the rights
17
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18
+ copies of the Software, and to permit persons to whom the Software is
19
+ furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all
22
+ copies or substantial portions of the Software.
23
+
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
+ SOFTWARE.
31
+ License-File: LICENSE
32
+ Keywords: analytics,observability,pipecat,roark,voice-ai
33
+ Classifier: Development Status :: 4 - Beta
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Programming Language :: Python :: 3.13
41
+ Classifier: Topic :: Multimedia :: Sound/Audio
42
+ Classifier: Topic :: Software Development :: Libraries
43
+ Requires-Python: >=3.10
44
+ Requires-Dist: httpx<1,>=0.27
45
+ Requires-Dist: pipecat-ai<1,>=0.0.40
46
+ Provides-Extra: dev
47
+ Requires-Dist: mypy>=1.10; extra == 'dev'
48
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
49
+ Requires-Dist: pytest>=8; extra == 'dev'
50
+ Requires-Dist: ruff>=0.6; extra == 'dev'
51
+ Provides-Extra: examples
52
+ Requires-Dist: python-dotenv>=1.0; extra == 'examples'
53
+ Description-Content-Type: text/markdown
54
+
55
+ # pipecat-roark
56
+
57
+ A [Roark](https://roark.ai) analytics observer for
58
+ [Pipecat](https://github.com/pipecat-ai/pipecat). Drop one observer into your
59
+ pipeline — Roark captures call lifecycle, transcripts, tool calls, and a
60
+ stereo audio recording. No other code changes required.
61
+
62
+ - **Tested with** `pipecat-ai` 0.0.108 (compatible with `>= 0.0.40, < 1`)
63
+ - **Python** 3.10+
64
+ - **Runtime-agnostic** — same code runs self-hosted *and* on Pipecat Cloud
65
+
66
+ > Maintained by [Roark](https://roark.ai). File issues at
67
+ > <https://github.com/roarkhq/pipecat-roark/issues>.
68
+
69
+ ---
70
+
71
+ ## Contents
72
+
73
+ - [Quick start](#quick-start)
74
+ - [How it works](#how-it-works)
75
+ - [Running modes](#running-modes)
76
+ - [Examples](#examples)
77
+ - [Advanced](#advanced)
78
+ - [Bring your own `AudioBufferProcessor`](#bring-your-own-audiobufferprocessor)
79
+ - [Handling WebRTC disconnects](#handling-webrtc-disconnects)
80
+ - [Correlating with OpenTelemetry tracing](#correlating-with-opentelemetry-tracing)
81
+ - [Troubleshooting](#troubleshooting)
82
+ - [Configuration reference](#configuration-reference)
83
+ - [Development](#development)
84
+ - [License](#license)
85
+
86
+ ---
87
+
88
+ ## Quick start
89
+
90
+ ### 1. Install
91
+
92
+ ```bash
93
+ pip install pipecat-roark
94
+ ```
95
+
96
+ ### 2. Configure
97
+
98
+ Set one env var:
99
+
100
+ ```bash
101
+ ROARK_API_KEY=rk_live_...
102
+ ```
103
+
104
+ > The Roark API key is all you configure — the observer knows its own service
105
+ > endpoints. `ROARK_API_KEY` can also be passed as `api_key=` to `RoarkObserver`.
106
+
107
+ ### 3. Wire the observer
108
+
109
+ Drop `RoarkObserver` into your pipeline's `observers=[...]` list. Splice the
110
+ auto-created `roark.audio_processor` **after `transport.output()`** so it sees
111
+ the bot's audio post-TTS:
112
+
113
+ ```python
114
+ from pipecat.pipeline.pipeline import Pipeline
115
+ from pipecat.pipeline.task import PipelineParams, PipelineTask
116
+ from pipecat_roark import RoarkObserver
117
+
118
+ roark = RoarkObserver(
119
+ api_key="rk_live_...",
120
+ agent_id="support-bot-v3",
121
+ agent_name="Support Bot v3",
122
+ agent_prompt=SYSTEM_PROMPT,
123
+ )
124
+
125
+ pipeline = Pipeline([
126
+ transport.input(), stt, context_aggregator.user(), llm, tts,
127
+ transport.output(),
128
+ roark.audio_processor, # after transport.output() — L=user, R=bot
129
+ context_aggregator.assistant(),
130
+ ])
131
+
132
+ task = PipelineTask(pipeline, params=PipelineParams(observers=[roark]))
133
+ ```
134
+
135
+ That's it — transcripts, tool calls, and the stereo recording flow to Roark
136
+ automatically.
137
+
138
+ ---
139
+
140
+ ## How it works
141
+
142
+ The observer subscribes to Pipecat frames and ships a compact event timeline
143
+ to Roark:
144
+
145
+ | Phase | What's captured |
146
+ |---|---|
147
+ | **Pipeline start** | `call-started` POST + recording begins. Agent is lazy-registered on Roark the first time it sees this `agent_id`. |
148
+ | **User turns** | Final `TranscriptionFrame`s (interim transcriptions ignored). |
149
+ | **Assistant turns** | `TTSTextFrame` chunks aggregated between `BotStoppedSpeakingFrame` / `InterruptionFrame` boundaries. |
150
+ | **Tool calls** | `FunctionCallInProgressFrame` + `FunctionCallResultFrame`, paired by `toolCallId`. |
151
+ | **Audio** | Stereo PCM chunks emitted by `AudioBufferProcessor`, streamed via presigned upload URLs (`POST /v1/integrations/pipecat/chunk-upload-url`). |
152
+ | **Pipeline end** | `EndFrame` / `CancelFrame` / `StopFrame` (or `aflush()`) flushes in-flight turns, drains uploads, and POSTs `call-ended`. Roark finalizes the recording on its side. |
153
+
154
+ Transcripts and tool calls are forwarded in Pipecat's native shape — Roark
155
+ maps them to its internal schema on its side.
156
+
157
+ ### Audio capture defaults
158
+
159
+ The observer always creates a sane-default
160
+ [`AudioBufferProcessor`](https://docs.pipecat.ai/server/utilities/audio/audio-recording)
161
+ (stereo, ~256 KB chunks) exposed as `roark.audio_processor`. The sample rate
162
+ is **adopted from the pipeline's `StartFrame`**, so it tracks whatever the
163
+ transport/provider negotiated — 8 kHz on Twilio/Telnyx, 16/24/48 kHz on
164
+ Daily/LiveKit, etc. The rate is forwarded to Roark as the recording sample
165
+ rate.
166
+
167
+ ### Failure mode
168
+
169
+ Failures are logged and swallowed — **the observer never raises into the
170
+ pipeline**. Your call keeps running even if Roark is unreachable.
171
+
172
+ ---
173
+
174
+ ## Running modes
175
+
176
+ `RoarkObserver` is **runtime-agnostic** — the same observer wiring works
177
+ whether your Pipecat agent runs as a self-hosted process or is deployed to
178
+ [Pipecat Cloud](https://docs.pipecat.daily.co/). Write one `bot(runner_args)`
179
+ entry point with Pipecat's
180
+ [`create_transport`](https://docs.pipecat.ai/server/utilities/runner) helper,
181
+ and the same file runs in both modes — see `examples/bot.py`.
182
+
183
+ | | Self-hosted | Pipecat Cloud |
184
+ |---|---|---|
185
+ | Entry point | `python bot.py` → `pipecat.runner.run.main()` dispatches to `bot()` | Platform invokes `bot(runner_args)` per session |
186
+ | Room/token | You provision (Daily REST, `pipecat.runner.daily.configure`, …) | Injected via `DailyRunnerArguments` |
187
+ | Env vars | `.env` / your secrets manager | `pcc secrets set <name> KEY=value …` |
188
+ | Teardown | `EndFrame` is reliable | Sessions can vanish — wire [`aflush()` on disconnect](#handling-webrtc-disconnects) |
189
+ | Observer wiring | ← identical → | ← identical → |
190
+
191
+ ### Self-hosted
192
+
193
+ ```bash
194
+ cp .env.example .env
195
+ # fill in ROARK_API_KEY
196
+ uv sync --all-extras
197
+ uv run python examples/bot.py --transport daily # or: --transport webrtc
198
+ ```
199
+
200
+ ### Pipecat Cloud
201
+
202
+ Set the same vars as deployment secrets, then deploy:
203
+
204
+ ```bash
205
+ pcc secrets set roark-secrets \
206
+ ROARK_API_KEY=rk_live_...
207
+
208
+ pcc deploy
209
+ pcc agent start <agent-name>
210
+ ```
211
+
212
+ Reference the secrets from your `pcc-deploy.toml` so the container sees them
213
+ as `os.environ["ROARK_API_KEY"]` (etc.) at runtime.
214
+
215
+ ---
216
+
217
+ ## Examples
218
+
219
+ Two example files ship with the package:
220
+
221
+ - **`examples/basic_observer.py`** — minimal transport-agnostic wiring sketch.
222
+ Shows where `RoarkObserver` and `roark.audio_processor` slot into a
223
+ `Pipeline` / `PipelineTask`. STT / LLM / TTS stages are omitted — copy them
224
+ into your own pipeline.
225
+ - **`examples/bot.py`** — runnable foundational voice assistant
226
+ (Deepgram STT → OpenAI LLM → Cartesia TTS) with `RoarkObserver` wired in.
227
+ Same file runs self-hosted (`--transport webrtc` / `--transport daily`)
228
+ **and** deploys to Pipecat Cloud unchanged.
229
+
230
+ ```bash
231
+ cp .env.example .env
232
+ # fill in:
233
+ # ROARK_API_KEY
234
+ # DEEPGRAM_API_KEY, OPENAI_API_KEY, CARTESIA_API_KEY
235
+
236
+ uv sync --all-extras
237
+ uv pip install "pipecat-ai[silero,deepgram,openai,cartesia,webrtc,daily]"
238
+
239
+ # Local browser via Pipecat's built-in WebRTC (no third-party transport account):
240
+ uv run python examples/bot.py --transport webrtc
241
+ # Or Daily (see Pipecat runner docs for transport-specific setup):
242
+ uv run python examples/bot.py --transport daily
243
+ ```
244
+
245
+ ---
246
+
247
+ ## Advanced
248
+
249
+ ### Bring your own `AudioBufferProcessor`
250
+
251
+ If you need to tune sample rate, channel count, or buffer size, instantiate
252
+ `AudioBufferProcessor` yourself and pass it via `audio_buffer_processor=`:
253
+
254
+ ```python
255
+ from pipecat.processors.audio.audio_buffer_processor import AudioBufferProcessor
256
+
257
+ audio_buffer = AudioBufferProcessor(sample_rate=16000, num_channels=1, buffer_size=128 * 1024)
258
+
259
+ pipeline = Pipeline([..., transport.output(), audio_buffer, ...])
260
+
261
+ RoarkObserver(
262
+ api_key="rk_live_...",
263
+ agent_id="support-bot-v3",
264
+ audio_buffer_processor=audio_buffer,
265
+ )
266
+ ```
267
+
268
+ ### Handling WebRTC disconnects
269
+
270
+ Pipecat's WebRTC transports (notably `SmallWebRTC`) sometimes tear down
271
+ without pushing `EndFrame` through observers. Call `aflush()` from the
272
+ disconnect handler to guarantee the call is finalized on Roark:
273
+
274
+ ```python
275
+ @transport.event_handler("on_client_disconnected")
276
+ async def _on_disconnect(_, __):
277
+ await roark_observer.aflush(reason="client-disconnected")
278
+ ```
279
+
280
+ `aflush()` is idempotent — the regular `EndFrame` path will no-op on the next call.
281
+
282
+ ### Correlating with OpenTelemetry tracing
283
+
284
+ If you also enable Pipecat's OpenTelemetry tracing
285
+ (`PipelineTask(enable_tracing=True)`), generate **one** call ID up front and
286
+ pass it to both sides — the observer's `pipecat_call_id` and `PipelineTask`'s
287
+ `conversation_id` — so each Roark call can be looked up by the same value in
288
+ your tracing backend:
289
+
290
+ ```python
291
+ import uuid
292
+ from pipecat.pipeline.task import PipelineParams, PipelineTask
293
+ from pipecat_roark import RoarkObserver
294
+
295
+ call_id = str(uuid.uuid4()) # or your own external ID (Twilio CallSid, DB row id, …)
296
+
297
+ roark = RoarkObserver(
298
+ api_key="rk_live_...",
299
+ agent_id="support-bot-v3",
300
+ pipecat_call_id=call_id, # appears on the Roark record as `pipecatCallId`
301
+ )
302
+
303
+ task = PipelineTask(
304
+ pipeline,
305
+ params=PipelineParams(observers=[roark]),
306
+ enable_tracing=True,
307
+ conversation_id=call_id, # set as the `conversation.id` span attribute by Pipecat
308
+ )
309
+ ```
310
+
311
+ Pipecat sets `conversation.id` as a **span attribute** on a root
312
+ `"conversation"` span (and propagates it to every child span). The OTel
313
+ `traceId` itself is auto-generated and unrelated to your call ID; correlation
314
+ happens by attribute value. To find the trace for a Roark call, query your
315
+ backend by `conversation.id = <pipecatCallId>` (e.g., Honeycomb:
316
+ `where conversation.id = "..."`, Jaeger: tag filter, Datadog:
317
+ `@conversation.id:...`).
318
+
319
+ > If you omit `pipecat_call_id`, the observer generates one internally — fine
320
+ > for standalone use, but you won't be able to link a Roark call to its trace.
321
+ > With OTel enabled, **always pass the same value to both**.
322
+
323
+ ---
324
+
325
+ ## Troubleshooting
326
+
327
+ <details>
328
+ <summary><strong>Do I need <code>enable_tracing=True</code> on <code>PipelineTask</code>?</strong></summary>
329
+
330
+ <br>
331
+
332
+ No. `RoarkObserver` captures raw frames — it does not consume OpenTelemetry
333
+ spans. The tracing flag is unrelated. If you *do* enable it and want Roark
334
+ calls linked to their traces, see
335
+ [Correlating with OpenTelemetry tracing](#correlating-with-opentelemetry-tracing).
336
+
337
+ </details>
338
+
339
+ <details>
340
+ <summary><strong>Calls aren't finalizing on Roark</strong></summary>
341
+
342
+ <br>
343
+
344
+ Some transports (notably `SmallWebRTC`) tear down without pushing `EndFrame`
345
+ through observers. Wire `aflush()` into your disconnect handler — see
346
+ [Handling WebRTC disconnects](#handling-webrtc-disconnects).
347
+
348
+ </details>
349
+
350
+ <details>
351
+ <summary><strong>Recording captures user audio only / bot audio only</strong></summary>
352
+
353
+ <br>
354
+
355
+ The `AudioBufferProcessor` must sit **after `transport.output()`** so it sees
356
+ the bot's audio post-TTS. If it's placed earlier in the pipeline, the bot
357
+ channel will be silent.
358
+
359
+ </details>
360
+
361
+ <details>
362
+ <summary><strong>Transcripts arrive empty</strong></summary>
363
+
364
+ <br>
365
+
366
+ The observer warns `call-ended with empty transcript ... no TranscriptionFrame
367
+ or TTSTextFrame was observed during the call` when nothing was captured.
368
+ Usually this means the STT service isn't emitting finalized
369
+ `TranscriptionFrame`s, or the pipeline ended before any speech was processed.
370
+
371
+ </details>
372
+
373
+ ---
374
+
375
+ ## Configuration reference
376
+
377
+ | Parameter | Type | Default | Notes |
378
+ |-----------|------|---------|-------|
379
+ | `api_key` | `str` | — | **Required.** Roark API key. |
380
+ | `agent_id` | `str` | — | **Required.** Customer-stable agent identifier. |
381
+ | `agent_name` | `str \| None` | `None` | Display name. |
382
+ | `agent_prompt` | `str \| None` | `None` | System prompt. Persisted as the agent's prompt revision. |
383
+ | `audio_buffer_processor` | `AudioBufferProcessor \| None` | `None` | Power-user override — pass your own `AudioBufferProcessor` to control sample rate / channels / buffer size. If omitted, the observer creates a default (stereo, ~256 KB chunks; sample rate adopted from the pipeline's `StartFrame`) accessible via `observer.audio_processor`. |
384
+ | `pipecat_call_id` | `str \| None` | random UUID | Stable call identifier. Pass the same value to `PipelineTask(conversation_id=...)` when OTel tracing is enabled — see [Correlating with OpenTelemetry tracing](#correlating-with-opentelemetry-tracing). |
385
+
386
+ ---
387
+
388
+ ## Development
389
+
390
+ ```bash
391
+ uv sync --all-extras
392
+ uv run pytest
393
+ uv run ruff check .
394
+ ```
395
+
396
+ ---
397
+
398
+ ## License
399
+
400
+ MIT — see [LICENSE](./LICENSE).