voicesafekit 0.6.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.
- voicesafekit-0.6.0/LICENSE +22 -0
- voicesafekit-0.6.0/PKG-INFO +425 -0
- voicesafekit-0.6.0/README.md +377 -0
- voicesafekit-0.6.0/pyproject.toml +81 -0
- voicesafekit-0.6.0/setup.cfg +4 -0
- voicesafekit-0.6.0/tests/test_audio.py +357 -0
- voicesafekit-0.6.0/tests/test_cli_encryption.py +53 -0
- voicesafekit-0.6.0/tests/test_cli_redact.py +22 -0
- voicesafekit-0.6.0/tests/test_encryption.py +23 -0
- voicesafekit-0.6.0/tests/test_engine.py +228 -0
- voicesafekit-0.6.0/tests/test_litellm.py +229 -0
- voicesafekit-0.6.0/tests/test_livekit.py +58 -0
- voicesafekit-0.6.0/tests/test_openvoiceos.py +24 -0
- voicesafekit-0.6.0/tests/test_openwebui.py +298 -0
- voicesafekit-0.6.0/tests/test_pipecat.py +151 -0
- voicesafekit-0.6.0/tests/test_session.py +59 -0
- voicesafekit-0.6.0/tests/test_spoken.py +215 -0
- voicesafekit-0.6.0/voicesafekit/__init__.py +21 -0
- voicesafekit-0.6.0/voicesafekit/__main__.py +5 -0
- voicesafekit-0.6.0/voicesafekit/audio.py +506 -0
- voicesafekit-0.6.0/voicesafekit/cli.py +297 -0
- voicesafekit-0.6.0/voicesafekit/encryption.py +94 -0
- voicesafekit-0.6.0/voicesafekit/engine.py +413 -0
- voicesafekit-0.6.0/voicesafekit/integrations/__init__.py +2 -0
- voicesafekit-0.6.0/voicesafekit/integrations/litellm.py +249 -0
- voicesafekit-0.6.0/voicesafekit/integrations/livekit.py +103 -0
- voicesafekit-0.6.0/voicesafekit/integrations/openvoiceos.py +61 -0
- voicesafekit-0.6.0/voicesafekit/integrations/openwebui.py +407 -0
- voicesafekit-0.6.0/voicesafekit/integrations/pipecat.py +234 -0
- voicesafekit-0.6.0/voicesafekit/models.py +77 -0
- voicesafekit-0.6.0/voicesafekit/rules.py +424 -0
- voicesafekit-0.6.0/voicesafekit/session.py +194 -0
- voicesafekit-0.6.0/voicesafekit/spoken.py +395 -0
- voicesafekit-0.6.0/voicesafekit.egg-info/PKG-INFO +425 -0
- voicesafekit-0.6.0/voicesafekit.egg-info/SOURCES.txt +37 -0
- voicesafekit-0.6.0/voicesafekit.egg-info/dependency_links.txt +1 -0
- voicesafekit-0.6.0/voicesafekit.egg-info/entry_points.txt +2 -0
- voicesafekit-0.6.0/voicesafekit.egg-info/requires.txt +25 -0
- voicesafekit-0.6.0/voicesafekit.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mohamad Kanso
|
|
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.
|
|
22
|
+
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: voicesafekit
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Catch spoken PII (card numbers, SSNs, emails said out loud) before voice transcripts reach an LLM. Zero dependencies, Pipecat and LiveKit drop-ins.
|
|
5
|
+
Author: Mohamad Kanso
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/MohamadKanso/VoiceSafeKit
|
|
8
|
+
Project-URL: Repository, https://github.com/MohamadKanso/VoiceSafeKit
|
|
9
|
+
Project-URL: Demo, https://mohamadkanso.github.io/VoiceSafeKit/
|
|
10
|
+
Project-URL: Changelog, https://github.com/MohamadKanso/VoiceSafeKit/blob/main/CHANGELOG.md
|
|
11
|
+
Project-URL: Issues, https://github.com/MohamadKanso/VoiceSafeKit/issues
|
|
12
|
+
Project-URL: Benchmark, https://github.com/MohamadKanso/VoiceSafeKit/tree/main/benchmarks
|
|
13
|
+
Keywords: voice-assistant,llm-safety,privacy,local-ai,openvoiceos,wyoming,pii-detection,conversation-safety,pii-redaction,speech-to-text,pipecat,livekit,voice-agents,guardrails,open-webui,litellm,audio-redaction
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Security
|
|
22
|
+
Classifier: Operating System :: OS Independent
|
|
23
|
+
Classifier: Intended Audience :: Information Technology
|
|
24
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Provides-Extra: crypto
|
|
30
|
+
Requires-Dist: cryptography>=42; extra == "crypto"
|
|
31
|
+
Provides-Extra: pipecat
|
|
32
|
+
Requires-Dist: pipecat-ai>=1.0; extra == "pipecat"
|
|
33
|
+
Provides-Extra: livekit
|
|
34
|
+
Requires-Dist: livekit-agents>=1.0; extra == "livekit"
|
|
35
|
+
Provides-Extra: audio
|
|
36
|
+
Requires-Dist: faster-whisper>=1.0; extra == "audio"
|
|
37
|
+
Requires-Dist: numpy; extra == "audio"
|
|
38
|
+
Provides-Extra: openwebui
|
|
39
|
+
Requires-Dist: pydantic>=2; extra == "openwebui"
|
|
40
|
+
Provides-Extra: litellm
|
|
41
|
+
Requires-Dist: litellm[proxy]>=1.80; extra == "litellm"
|
|
42
|
+
Provides-Extra: dev
|
|
43
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
44
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
45
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
46
|
+
Requires-Dist: cryptography>=42; extra == "dev"
|
|
47
|
+
Dynamic: license-file
|
|
48
|
+
|
|
49
|
+
<p align="center">
|
|
50
|
+
<img src="https://raw.githubusercontent.com/MohamadKanso/VoiceSafeKit/main/docs/assets/voicesafekit-cover.svg" alt="VoiceSafeKit" width="760">
|
|
51
|
+
</p>
|
|
52
|
+
|
|
53
|
+
<p align="center">
|
|
54
|
+
<a href="https://github.com/MohamadKanso/VoiceSafeKit/actions/workflows/ci.yml"><img src="https://github.com/MohamadKanso/VoiceSafeKit/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
55
|
+
<img src="https://img.shields.io/badge/dependencies-0-00ff84" alt="Zero dependencies">
|
|
56
|
+
<img src="https://img.shields.io/badge/python-3.10%E2%80%933.13-00ff84" alt="Python 3.10-3.13">
|
|
57
|
+
<img src="https://img.shields.io/badge/license-MIT-00ff84" alt="MIT">
|
|
58
|
+
</p>
|
|
59
|
+
|
|
60
|
+
<p align="center">
|
|
61
|
+
<a href="https://mohamadkanso.github.io/VoiceSafeKit/"><strong>Try it in your browser</strong></a>
|
|
62
|
+
·
|
|
63
|
+
<a href="#spokenpii-benchmark"><strong>Benchmark</strong></a>
|
|
64
|
+
·
|
|
65
|
+
<a href="#pipecat">Pipecat</a>
|
|
66
|
+
·
|
|
67
|
+
<a href="#livekit-agents">LiveKit</a>
|
|
68
|
+
·
|
|
69
|
+
<a href="#open-webui">Open WebUI</a>
|
|
70
|
+
·
|
|
71
|
+
<a href="#litellm">LiteLLM</a>
|
|
72
|
+
·
|
|
73
|
+
<a href="#bleep-the-recording-not-just-the-transcript">Audio</a>
|
|
74
|
+
·
|
|
75
|
+
<a href="https://github.com/MohamadKanso/VoiceSafeKit/blob/main/CHANGELOG.md">Changelog</a>
|
|
76
|
+
</p>
|
|
77
|
+
|
|
78
|
+
# VoiceSafeKit
|
|
79
|
+
|
|
80
|
+
**Your voice agent is sending card numbers to the LLM, and your PII filter can't see them.**
|
|
81
|
+
|
|
82
|
+
<p align="center">
|
|
83
|
+
<img src="https://raw.githubusercontent.com/MohamadKanso/VoiceSafeKit/main/docs/assets/demo.gif" alt="A spoken card number and email are recorded in the browser, transcribed by Whisper on-device, and both are flagged for redaction" width="860">
|
|
84
|
+
<br>
|
|
85
|
+
<sub>A voice says a card number and an email into the <a href="https://mohamadkanso.github.io/VoiceSafeKit/">live app</a>. Whisper runs on-device and both are caught. Recording sped up 5×.</sub>
|
|
86
|
+
</p>
|
|
87
|
+
|
|
88
|
+
Speech-to-text writes what people *say*, not what they *mean*:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
my card is four two four two four two four two four two four two four two four two
|
|
92
|
+
email me at alex dot rivera at gmail dot com
|
|
93
|
+
call me on oh seven seven double oh nine double oh one two three
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Regex filters and NER models (Presidio, most guardrail libraries) are built for written
|
|
97
|
+
text, so they pass all three straight through. VoiceSafeKit reads speech the way STT
|
|
98
|
+
writes it, redacts the sensitive parts, and gives the values back when the agent needs
|
|
99
|
+
to say them. It can also bleep them out of the call recording. Zero dependencies, about
|
|
100
|
+
0.06 ms per utterance, and drop-ins for **Pipecat**, **LiveKit Agents**, **Open WebUI** and
|
|
101
|
+
**LiteLLM**.
|
|
102
|
+
|
|
103
|
+
```text
|
|
104
|
+
User says : Hi, my name is Alex Rivera. My card number is four two four two four two four two
|
|
105
|
+
four two four two four two four two and my email is alex dot rivera at gmail dot com.
|
|
106
|
+
LLM receives : Hi, my name is [NAME_1]. My card number is [CARD_1] and my email is [EMAIL_1].
|
|
107
|
+
TTS says : Thanks Alex Rivera. I'll send the receipt to alex dot rivera at gmail dot com.
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
<sub>Output of `examples/pipecat/offline_demo.py`, which runs without API keys.</sub>
|
|
111
|
+
|
|
112
|
+
## Install
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pip install voicesafekit
|
|
116
|
+
# or, before the first PyPI release: pip install git+https://github.com/MohamadKanso/VoiceSafeKit
|
|
117
|
+
# extras: [pipecat] [livekit] [openwebui] [litellm] [audio] [crypto]
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
echo "my pin is four eight two one" | voicesafekit redact
|
|
122
|
+
# my pin is [PIN removed]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## See it
|
|
126
|
+
|
|
127
|
+
Spoken PII caught in the [live app](https://mohamadkanso.github.io/VoiceSafeKit/), with no
|
|
128
|
+
digits or @ signs anywhere in the transcript:
|
|
129
|
+
|
|
130
|
+
<p align="center">
|
|
131
|
+
<img src="https://raw.githubusercontent.com/MohamadKanso/VoiceSafeKit/main/docs/assets/screenshot-1-transcript.png" alt="Capture and Check panels: a spoken card number, email and one-time code are each flagged and marked caught in spoken form" width="900">
|
|
132
|
+
</p>
|
|
133
|
+
|
|
134
|
+
<table>
|
|
135
|
+
<tr>
|
|
136
|
+
<td width="50%"><img src="https://raw.githubusercontent.com/MohamadKanso/VoiceSafeKit/main/docs/assets/screenshot-2-risks.png" alt="Inspect tab highlighting the spoken name, card number, email and code in the original transcript"></td>
|
|
137
|
+
<td width="50%"><img src="https://raw.githubusercontent.com/MohamadKanso/VoiceSafeKit/main/docs/assets/screenshot-3-protect.png" alt="Protect panel with the safe transcript and guidance for the assistant"></td>
|
|
138
|
+
</tr>
|
|
139
|
+
<tr>
|
|
140
|
+
<td><sub>Inspect: the exact spoken words that were flagged.</sub></td>
|
|
141
|
+
<td><sub>Protect: the safe transcript and guidance for the assistant.</sub></td>
|
|
142
|
+
</tr>
|
|
143
|
+
</table>
|
|
144
|
+
|
|
145
|
+
<p align="center">
|
|
146
|
+
<img src="https://raw.githubusercontent.com/MohamadKanso/VoiceSafeKit/main/docs/assets/screenshot-4-spoken-vs-text.png" alt="Table comparing a text-only filter, which marks spoken PII as SAFE, with VoiceSafeKit, which redacts it" width="900">
|
|
147
|
+
</p>
|
|
148
|
+
|
|
149
|
+
## SpokenPII benchmark
|
|
150
|
+
|
|
151
|
+
No public benchmark measures PII *as speech-to-text writes it*, so this repo ships one.
|
|
152
|
+
|
|
153
|
+
**Held-out real speech (the honest number).** New scripts read aloud by a British TTS
|
|
154
|
+
voice (`Daniel`), transcribed by faster-whisper `small.en`, frozen before any 0.6 change
|
|
155
|
+
and never used for tuning. A sample counts as protected only if the value can't be
|
|
156
|
+
recovered from the redacted transcript.
|
|
157
|
+
|
|
158
|
+
| Detector | PII protected (not leaked) | False alarms on no-PII |
|
|
159
|
+
|---|---:|---:|
|
|
160
|
+
| **VoiceSafeKit 0.6** | **98%** (106/108) | **2%** (1/40) |
|
|
161
|
+
| VoiceSafeKit 0.5 | 94% (102/108) | 2% (1/40) |
|
|
162
|
+
| VoiceSafeKit 0.6, spoken mode off | 87% (94/108) | 2% (1/40) |
|
|
163
|
+
| Microsoft Presidio 2.2 | 65% (70/108) | 42% (17/40) |
|
|
164
|
+
|
|
165
|
+
The three failures are published in
|
|
166
|
+
[#9](https://github.com/MohamadKanso/VoiceSafeKit/issues/9): a PIN Whisper wrote as money
|
|
167
|
+
(`$4,990`), a comma before the birth year, and a mangled date read as a phone number.
|
|
168
|
+
|
|
169
|
+
<details>
|
|
170
|
+
<summary>First real-speech set (Samantha voice, Whisper base.en)</summary>
|
|
171
|
+
|
|
172
|
+
v0.5 protected 101/108 on this set. Its 7 misses (comma-split digit groups, `SSN609507896`,
|
|
173
|
+
digits written as decimals, "fin" for PIN) were fixed in 0.6
|
|
174
|
+
([#6](https://github.com/MohamadKanso/VoiceSafeKit/issues/6)), so 0.6 now scores 108/108
|
|
175
|
+
here. Because the fixes were written against these misses, this set is no longer held out.
|
|
176
|
+
Presidio: 79/108, 17/40 false alarms.
|
|
177
|
+
|
|
178
|
+
</details>
|
|
179
|
+
|
|
180
|
+
**SpokenPII (synthetic).** 540 labelled sentences, the same values written two ways, plus
|
|
181
|
+
40 tricky no-PII requests ("emergency fund", "call Sue", "twenty five minutes"). A span
|
|
182
|
+
counts as protected only if at least 90% of it is masked.
|
|
183
|
+
|
|
184
|
+
| Detector | Written PII protected | Spoken PII protected | False alarms on no-PII | Median latency |
|
|
185
|
+
|---|---:|---:|---:|---:|
|
|
186
|
+
| **VoiceSafeKit 0.6** | **100%** | **100%** | **0%** | **0.06 ms** |
|
|
187
|
+
| VoiceSafeKit 0.6, spoken mode off | 100% | 11% | 0% | 0.04 ms |
|
|
188
|
+
| Microsoft Presidio 2.2 (spaCy `en_core_web_lg`) | 86% | 21% | 45% | ~3 ms |
|
|
189
|
+
|
|
190
|
+
Spoken PII protected, by type:
|
|
191
|
+
|
|
192
|
+
| Type | VoiceSafeKit | Presidio |
|
|
193
|
+
|---|---:|---:|
|
|
194
|
+
| Card number | 100% | 0% |
|
|
195
|
+
| SSN | 100% | 3% |
|
|
196
|
+
| Phone | 100% | 0% |
|
|
197
|
+
| Email | 100% | 0% |
|
|
198
|
+
| PIN | 100% | 13% |
|
|
199
|
+
| One-time code | 100% | 3% |
|
|
200
|
+
| Passport | 100% | 0% |
|
|
201
|
+
| Date of birth | 100% | 77% |
|
|
202
|
+
| Name | 100% | 97% |
|
|
203
|
+
|
|
204
|
+
**Read these numbers honestly.** I wrote the synthetic set and the detector, so 100% there
|
|
205
|
+
mostly shows the templates are covered; the held-out real-speech set is the fairer test. Presidio is a
|
|
206
|
+
general-purpose tool configured with its defaults, not tuned for speech. Its false alarms
|
|
207
|
+
are mostly `DATE_TIME` and `PERSON` hits on ordinary requests. Reproduce everything with
|
|
208
|
+
`python benchmarks/run.py --presidio` and
|
|
209
|
+
`python benchmarks/asr.py score --presidio --data benchmarks/data/spokenpii-asr-heldout.jsonl`.
|
|
210
|
+
Better still, add transcripts from your own STT engine.
|
|
211
|
+
|
|
212
|
+
## What it catches
|
|
213
|
+
|
|
214
|
+
| Category | Detectors |
|
|
215
|
+
|---|---|
|
|
216
|
+
| Spoken forms | digits as words, "oh" / "double" / "triple", "plus four four", tens and hundreds, spoken emails, spoken dates |
|
|
217
|
+
| Payment | card number (Luhn + context), partial card ("ends in 4821"), CVV, PIN, IBAN (mod-97) |
|
|
218
|
+
| Identity | SSN (with or without dashes), passport, UK National Insurance, date of birth, self-introduced name |
|
|
219
|
+
| Contact | email, phone, street address, UK postcode, IP address |
|
|
220
|
+
| Credentials | passwords and secrets in speech, one-time / verification codes, API keys (OpenAI, AWS, GitHub, Slack, Google) |
|
|
221
|
+
| Safety signals | emergency, emotional distress, coercion, medical / legal / financial advice requests |
|
|
222
|
+
|
|
223
|
+
Every result has a `decision` (`SAFE`, `REDACT`, `REVIEW`, `BLOCK`), a 0-100 `score`,
|
|
224
|
+
per-finding `confidence` and `spoken` flags, a `safe_transcript`, a `redaction_map` and
|
|
225
|
+
plain-English `assistant_guidance`.
|
|
226
|
+
|
|
227
|
+
## Python API
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from voicesafekit import analyze_transcript
|
|
231
|
+
|
|
232
|
+
result = analyze_transcript("my social is four seven one, five five, eight eight four three")
|
|
233
|
+
result.decision # "BLOCK"
|
|
234
|
+
result.safe_transcript # "my social is [SSN removed]"
|
|
235
|
+
result.findings[0].spoken # True
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Reversible placeholders
|
|
239
|
+
|
|
240
|
+
Plain redaction breaks agents that need to say "I've sent it to your email". A
|
|
241
|
+
`PrivacySession` swaps values for placeholders, keeps the map in memory, and restores
|
|
242
|
+
them in the reply, including streamed tokens:
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from voicesafekit import PrivacySession
|
|
246
|
+
|
|
247
|
+
session = PrivacySession()
|
|
248
|
+
safe = session.protect("email me at alex dot rivera at gmail dot com")
|
|
249
|
+
safe.text # "email me at [EMAIL_1]"
|
|
250
|
+
reply = llm(safe.text) # "Done, I've emailed [EMAIL_1]."
|
|
251
|
+
session.restore(reply) # "Done, I've emailed alex dot rivera at gmail dot com."
|
|
252
|
+
|
|
253
|
+
stream = session.stream() # for token streams: "[EMA" + "IL_1]" restores correctly
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The same value gets the same placeholder for the whole conversation, whether it was
|
|
257
|
+
spoken or written. **Passwords, PINs, CVVs, one-time codes and API keys are never stored,**
|
|
258
|
+
so the model can't make the voice read them back.
|
|
259
|
+
|
|
260
|
+
## Pipecat
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
from voicesafekit.integrations.pipecat import voicesafe_processors
|
|
264
|
+
|
|
265
|
+
guard_in, restore_out, guard_context = voicesafe_processors()
|
|
266
|
+
|
|
267
|
+
pipeline = Pipeline([
|
|
268
|
+
transport.input(), stt,
|
|
269
|
+
guard_in, # spoken PII -> [CARD_1], [EMAIL_1] before the LLM
|
|
270
|
+
user_aggregator, llm,
|
|
271
|
+
restore_out, # real values back for the voice only
|
|
272
|
+
tts, transport.output(),
|
|
273
|
+
guard_context, # stored assistant replies keep the placeholders
|
|
274
|
+
assistant_aggregator,
|
|
275
|
+
])
|
|
276
|
+
|
|
277
|
+
@guard_in.event_handler("on_block")
|
|
278
|
+
async def on_block(processor, protected):
|
|
279
|
+
... # emergencies, government IDs, CVVs: never forwarded, refusal is spoken
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Many STT services send several final transcriptions per turn. When one ends mid-number near
|
|
283
|
+
a sensitive word ("my card number is four two four two"), the processor holds it for up to
|
|
284
|
+
0.8 s and merges it with the next, so a card number split across frames is still caught.
|
|
285
|
+
|
|
286
|
+
## LiveKit Agents
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
from voicesafekit.integrations.livekit import VoiceSafeAgent
|
|
290
|
+
|
|
291
|
+
class Assistant(VoiceSafeAgent): # instead of Agent
|
|
292
|
+
def __init__(self):
|
|
293
|
+
super().__init__(instructions="You are a helpful voice assistant.")
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
`VoiceSafeAgent` protects each user turn in `on_user_turn_completed`, restores values in
|
|
297
|
+
`tts_node`, and stops blocked turns. `VoiceSafeRedactor().redact(text)` is a stateless,
|
|
298
|
+
idempotent redactor for transcript and log hooks. Full example: `examples/livekit/agent.py`.
|
|
299
|
+
|
|
300
|
+
## Open WebUI
|
|
301
|
+
|
|
302
|
+
A Filter Function: **Admin Panel > Functions > + > paste
|
|
303
|
+
[`examples/openwebui/voicesafekit_filter.py`](https://github.com/MohamadKanso/VoiceSafeKit/blob/main/examples/openwebui/voicesafekit_filter.py) > Save**,
|
|
304
|
+
then enable it. It installs VoiceSafeKit through its `requirements:` line.
|
|
305
|
+
|
|
306
|
+
- Every user message is protected before the model sees it ("my card is four two four
|
|
307
|
+
two ..." becomes "my card is [CARD_1]"), and real values come back in the reply on the
|
|
308
|
+
user's own screen, streamed or not.
|
|
309
|
+
- Placeholders stay consistent per chat and per user. A new message carrying a government
|
|
310
|
+
ID, card security code or emergency is stopped with a clear message (valve
|
|
311
|
+
`block_on_critical`).
|
|
312
|
+
- Valves: `spoken`, `block_on_critical`, `restore_in_output`, `max_chats`, `priority`.
|
|
313
|
+
|
|
314
|
+
Open WebUI's background tasks (title, tags, follow-ups, autocomplete) call the task model
|
|
315
|
+
directly and skip filters, so turn them off or use a local task model for full privacy.
|
|
316
|
+
Built against Open WebUI 0.13 and tested with the request shapes from its source.
|
|
317
|
+
|
|
318
|
+
## LiteLLM
|
|
319
|
+
|
|
320
|
+
A custom guardrail for the LiteLLM proxy, so every app behind it gets the same protection:
|
|
321
|
+
|
|
322
|
+
```yaml
|
|
323
|
+
guardrails:
|
|
324
|
+
- guardrail_name: voicesafekit
|
|
325
|
+
litellm_params:
|
|
326
|
+
guardrail: voicesafekit.integrations.litellm.VoiceSafeGuardrail
|
|
327
|
+
mode: [pre_call, post_call] # redact before the model, restore after (streaming too)
|
|
328
|
+
default_on: true
|
|
329
|
+
block_on_critical: true # HTTP 400 for government IDs, CVVs, emergencies
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
pip install "voicesafekit[litellm]"
|
|
334
|
+
litellm --config config.yaml
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Tested on a running LiteLLM 1.108 proxy: the upstream model received `my card is [CARD_1]`,
|
|
338
|
+
the client got the real values back (normal and streamed), and an SSN request returned
|
|
339
|
+
HTTP 400 without reaching the model. See [`examples/litellm/`](https://github.com/MohamadKanso/VoiceSafeKit/tree/main/examples/litellm/).
|
|
340
|
+
|
|
341
|
+
## Bleep the recording, not just the transcript
|
|
342
|
+
|
|
343
|
+
Redacting the transcript still leaves the card number in the call recording. VoiceSafeKit
|
|
344
|
+
bleeps it out of the audio too, locally and for free, with standard-library code once you
|
|
345
|
+
have word timestamps.
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
pip install "voicesafekit[audio]" # adds faster-whisper for word timestamps
|
|
349
|
+
voicesafekit bleep call.wav -o call.bleeped.wav
|
|
350
|
+
# Hi, my name is [name removed], my card number is [payment number removed] and my pin is [PIN removed].
|
|
351
|
+
#
|
|
352
|
+
# Bleeped 3 span(s) -> call.bleeped.wav
|
|
353
|
+
# 00:00.950 - 00:01.870 Person name
|
|
354
|
+
# 00:02.990 - 00:07.330 Payment card-like number
|
|
355
|
+
# 00:07.730 - 00:08.850 PIN
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Already have word timings from Deepgram, Whisper or WhisperX? Pass them and no extras are
|
|
359
|
+
needed:
|
|
360
|
+
|
|
361
|
+
```python
|
|
362
|
+
from voicesafekit.audio import bleep_wav, redact_audio_spans, words_from_deepgram
|
|
363
|
+
|
|
364
|
+
redaction = redact_audio_spans(words_from_deepgram(response)) # PII -> time spans
|
|
365
|
+
bleep_wav("call.wav", "call.bleeped.wav", redaction) # or mode="silence"
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Checked end to end: transcribing the bleeped file with Whisper finds no card or PIN digits,
|
|
369
|
+
while the rest of the sentence still comes through. PCM WAV (8/16/24/32-bit, any channel count), streamed in
|
|
370
|
+
chunks; every sample outside the spans is copied byte for byte.
|
|
371
|
+
|
|
372
|
+
## OpenVoiceOS
|
|
373
|
+
|
|
374
|
+
```python
|
|
375
|
+
from voicesafekit.integrations.openvoiceos import protect_utterance
|
|
376
|
+
|
|
377
|
+
result = protect_utterance(utterance)
|
|
378
|
+
if result.should_continue:
|
|
379
|
+
send_to_llm(result.safe_transcript)
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## CLI
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
voicesafekit check examples/transcripts/identity_theft.txt --pretty
|
|
386
|
+
voicesafekit redact --placeholders < transcript.txt
|
|
387
|
+
voicesafekit conversation turn1.txt turn2.txt --pretty
|
|
388
|
+
voicesafekit bleep call.wav -o call.bleeped.wav # needs [audio] or --words
|
|
389
|
+
voicesafekit check transcript.txt --out result.enc.json # AES-256-GCM, needs [crypto]
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Browser app
|
|
393
|
+
|
|
394
|
+
The [live app](https://mohamadkanso.github.io/VoiceSafeKit/) records your voice,
|
|
395
|
+
transcribes it with Whisper on your device and runs the same rules in the browser. No
|
|
396
|
+
server and no API key, and nothing leaves the page. Say a card number out loud and watch
|
|
397
|
+
it disappear.
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
python3 -m http.server 8124 --directory docs # run it locally
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
## Design principles
|
|
404
|
+
|
|
405
|
+
- **Speech first.** Rules run on what STT actually produces, not on tidy written text.
|
|
406
|
+
- **Local and tiny.** Pure Python, no dependencies, no network, no model download.
|
|
407
|
+
- **Auditable.** Every finding has a confidence score and maps to exact spans.
|
|
408
|
+
- **Fail toward privacy.** Credentials are never stored, blocked turns never reach the model.
|
|
409
|
+
- **Honest scope.** It is a fast first layer, not a full privacy programme or a
|
|
410
|
+
compliance certification. Pair it with your own review for PCI DSS, HIPAA or GDPR work.
|
|
411
|
+
|
|
412
|
+
## Contributing
|
|
413
|
+
|
|
414
|
+
The most valuable contribution is a transcript your STT engine produced that VoiceSafeKit
|
|
415
|
+
missed (fake data only). Use the
|
|
416
|
+
[Missed spoken PII](https://github.com/MohamadKanso/VoiceSafeKit/issues/new?template=missed_spoken_pii.yml)
|
|
417
|
+
template. See [CONTRIBUTING.md](https://github.com/MohamadKanso/VoiceSafeKit/blob/main/CONTRIBUTING.md) and the [roadmap](https://github.com/MohamadKanso/VoiceSafeKit/blob/main/ROADMAP.md).
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
pip install -e ".[dev]" && pytest && python benchmarks/run.py
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
## License
|
|
424
|
+
|
|
425
|
+
MIT. See [LICENSE](https://github.com/MohamadKanso/VoiceSafeKit/blob/main/LICENSE). Not legal, medical or security advice.
|