pyveil 0.2.2__tar.gz → 0.2.3__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 (97) hide show
  1. {pyveil-0.2.2 → pyveil-0.2.3}/AGENTS.md +3 -0
  2. {pyveil-0.2.2 → pyveil-0.2.3}/CHANGELOG.md +17 -0
  3. {pyveil-0.2.2 → pyveil-0.2.3}/PKG-INFO +71 -2
  4. {pyveil-0.2.2 → pyveil-0.2.3}/README.md +65 -1
  5. {pyveil-0.2.2 → pyveil-0.2.3}/docs/README.md +1 -0
  6. {pyveil-0.2.2 → pyveil-0.2.3}/docs/cookbook.md +93 -8
  7. {pyveil-0.2.2 → pyveil-0.2.3}/docs/evaluation.html +1 -1
  8. {pyveil-0.2.2 → pyveil-0.2.3}/docs/index.html +6 -1
  9. pyveil-0.2.3/docs/integrations/ollama.md +134 -0
  10. {pyveil-0.2.2 → pyveil-0.2.3}/docs/llms.txt +1 -0
  11. {pyveil-0.2.2 → pyveil-0.2.3}/docs/manual.html +75 -9
  12. {pyveil-0.2.2 → pyveil-0.2.3}/docs/release-checklist.md +5 -4
  13. pyveil-0.2.3/examples/ollama.env.example +11 -0
  14. pyveil-0.2.3/examples/ollama.example.yaml +15 -0
  15. pyveil-0.2.3/examples/ollama.py +10 -0
  16. {pyveil-0.2.2 → pyveil-0.2.3}/llms.txt +3 -0
  17. {pyveil-0.2.2 → pyveil-0.2.3}/pyproject.toml +7 -1
  18. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/__init__.py +1 -1
  19. pyveil-0.2.3/pyveil/integrations/ollama.py +450 -0
  20. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil.egg-info/PKG-INFO +71 -2
  21. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil.egg-info/SOURCES.txt +6 -0
  22. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil.egg-info/requires.txt +6 -0
  23. {pyveil-0.2.2 → pyveil-0.2.3}/tests/test_cli.py +16 -1
  24. pyveil-0.2.3/tests/test_ollama_example.py +381 -0
  25. {pyveil-0.2.2 → pyveil-0.2.3}/CONTRIBUTING.md +0 -0
  26. {pyveil-0.2.2 → pyveil-0.2.3}/LICENSE +0 -0
  27. {pyveil-0.2.2 → pyveil-0.2.3}/MANIFEST.in +0 -0
  28. {pyveil-0.2.2 → pyveil-0.2.3}/SECURITY.md +0 -0
  29. {pyveil-0.2.2 → pyveil-0.2.3}/docs/detector-provenance.md +0 -0
  30. {pyveil-0.2.2 → pyveil-0.2.3}/docs/faq.md +0 -0
  31. {pyveil-0.2.2 → pyveil-0.2.3}/docs/guides/index.html +0 -0
  32. {pyveil-0.2.2 → pyveil-0.2.3}/docs/guides/mcp-pii-redaction.html +0 -0
  33. {pyveil-0.2.2 → pyveil-0.2.3}/docs/guides/python-llm-pii-redaction.html +0 -0
  34. {pyveil-0.2.2 → pyveil-0.2.3}/docs/guides/pyveil-vs-presidio.html +0 -0
  35. {pyveil-0.2.2 → pyveil-0.2.3}/docs/guides/style.css +0 -0
  36. {pyveil-0.2.2 → pyveil-0.2.3}/docs/integrations/logging.md +0 -0
  37. {pyveil-0.2.2 → pyveil-0.2.3}/docs/integrations/mcp.md +0 -0
  38. {pyveil-0.2.2 → pyveil-0.2.3}/docs/integrations/tracing.md +0 -0
  39. {pyveil-0.2.2 → pyveil-0.2.3}/docs/known-limitations.md +0 -0
  40. {pyveil-0.2.2 → pyveil-0.2.3}/docs/media/favicon.svg +0 -0
  41. {pyveil-0.2.2 → pyveil-0.2.3}/docs/media/pyveil-awesome-demo.gif +0 -0
  42. {pyveil-0.2.2 → pyveil-0.2.3}/docs/media/social-preview.html +0 -0
  43. {pyveil-0.2.2 → pyveil-0.2.3}/docs/media/social-preview.png +0 -0
  44. {pyveil-0.2.2 → pyveil-0.2.3}/docs/redaction-reference.md +0 -0
  45. {pyveil-0.2.2 → pyveil-0.2.3}/docs/roadmap.md +0 -0
  46. {pyveil-0.2.2 → pyveil-0.2.3}/docs/robots.txt +0 -0
  47. {pyveil-0.2.2 → pyveil-0.2.3}/docs/sitemap.xml +0 -0
  48. {pyveil-0.2.2 → pyveil-0.2.3}/docs/threat-model.md +0 -0
  49. {pyveil-0.2.2 → pyveil-0.2.3}/evaluation/README.md +0 -0
  50. {pyveil-0.2.2 → pyveil-0.2.3}/evaluation/corpus.json +0 -0
  51. {pyveil-0.2.2 → pyveil-0.2.3}/evaluation/evaluate.py +0 -0
  52. {pyveil-0.2.2 → pyveil-0.2.3}/examples/azure_openai.env.example +0 -0
  53. {pyveil-0.2.2 → pyveil-0.2.3}/examples/azure_openai.example.yaml +0 -0
  54. {pyveil-0.2.2 → pyveil-0.2.3}/examples/azure_openai.py +0 -0
  55. {pyveil-0.2.2 → pyveil-0.2.3}/examples/basic_usage.py +0 -0
  56. {pyveil-0.2.2 → pyveil-0.2.3}/examples/custom_rules.py +0 -0
  57. {pyveil-0.2.2 → pyveil-0.2.3}/examples/fastapi_middleware.py +0 -0
  58. {pyveil-0.2.2 → pyveil-0.2.3}/examples/litellm_proxy_filter.py +0 -0
  59. {pyveil-0.2.2 → pyveil-0.2.3}/examples/llm_client_boundary.py +0 -0
  60. {pyveil-0.2.2 → pyveil-0.2.3}/examples/log_filter.py +0 -0
  61. {pyveil-0.2.2 → pyveil-0.2.3}/examples/mcp_server_wrapper.py +0 -0
  62. {pyveil-0.2.2 → pyveil-0.2.3}/examples/mcp_tool_result.py +0 -0
  63. {pyveil-0.2.2 → pyveil-0.2.3}/examples/memory_write.py +0 -0
  64. {pyveil-0.2.2 → pyveil-0.2.3}/examples/openai_agents_guardrail.py +0 -0
  65. {pyveil-0.2.2 → pyveil-0.2.3}/examples/redact_json.py +0 -0
  66. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/__main__.py +0 -0
  67. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/cli.py +0 -0
  68. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/constants.py +0 -0
  69. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/core.py +0 -0
  70. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/detectors.py +0 -0
  71. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/exceptions.py +0 -0
  72. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/findings.py +0 -0
  73. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/integrations/__init__.py +0 -0
  74. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/integrations/azure_openai.py +0 -0
  75. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/integrations/logging.py +0 -0
  76. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/integrations/mcp.py +0 -0
  77. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/levels.py +0 -0
  78. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/masker.py +0 -0
  79. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/masking.py +0 -0
  80. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/patterns.py +0 -0
  81. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/placeholders.py +0 -0
  82. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/policy.py +0 -0
  83. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/py.typed +0 -0
  84. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/rules.py +0 -0
  85. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil/utils.py +0 -0
  86. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil.egg-info/dependency_links.txt +0 -0
  87. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil.egg-info/entry_points.txt +0 -0
  88. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil.egg-info/top_level.txt +0 -0
  89. {pyveil-0.2.2 → pyveil-0.2.3}/pyveil.yaml +0 -0
  90. {pyveil-0.2.2 → pyveil-0.2.3}/setup.cfg +0 -0
  91. {pyveil-0.2.2 → pyveil-0.2.3}/setup.py +0 -0
  92. {pyveil-0.2.2 → pyveil-0.2.3}/tests/__init__.py +0 -0
  93. {pyveil-0.2.2 → pyveil-0.2.3}/tests/test_azure_openai_example.py +0 -0
  94. {pyveil-0.2.2 → pyveil-0.2.3}/tests/test_custom_rules.py +0 -0
  95. {pyveil-0.2.2 → pyveil-0.2.3}/tests/test_masker.py +0 -0
  96. {pyveil-0.2.2 → pyveil-0.2.3}/tests/test_patterns.py +0 -0
  97. {pyveil-0.2.2 → pyveil-0.2.3}/tests/test_utils.py +0 -0
@@ -19,6 +19,7 @@ Do not turn pyveil into a general DLP suite, gateway, Presidio clone, or prompt-
19
19
  - Run CLI demo: `python3 -m pyveil demo`
20
20
  - Run CLI locally: `PYVEIL_SECRET=dev-secret python3 -m pyveil redact <file>`
21
21
  - Run detector evaluation: `python3 evaluation/evaluate.py --check`
22
+ - Run the Ollama boundary without a model call: `PYVEIL_SECRET=dev-secret python3 -m pyveil.integrations.ollama --dry-run`
22
23
 
23
24
  ## Architecture
24
25
 
@@ -72,6 +73,7 @@ This repository includes files meant for coding agents and LLM readers:
72
73
  - [docs/evaluation.html](docs/evaluation.html)
73
74
  - [docs/guides/](docs/guides/)
74
75
  - [docs/integrations/mcp.md](docs/integrations/mcp.md)
76
+ - [docs/integrations/ollama.md](docs/integrations/ollama.md)
75
77
  - [docs/integrations/logging.md](docs/integrations/logging.md)
76
78
  - [docs/integrations/tracing.md](docs/integrations/tracing.md)
77
79
 
@@ -88,6 +90,7 @@ Examples:
88
90
  - [examples/fastapi_middleware.py](examples/fastapi_middleware.py)
89
91
  - [examples/litellm_proxy_filter.py](examples/litellm_proxy_filter.py)
90
92
  - [examples/azure_openai.py](examples/azure_openai.py)
93
+ - [examples/ollama.py](examples/ollama.py)
91
94
 
92
95
  ## Development
93
96
 
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.3 - 2026-07-16
4
+
5
+ Local Ollama boundary release.
6
+
7
+ ### Added
8
+
9
+ - Installable Ollama chat integration with env, `.env`, and YAML configuration,
10
+ a dry-run boundary, response metrics, and configurable context/keep-alive limits.
11
+ - A tested `qwen3.5:4b` local-model recipe sized for a 16GB Apple silicon Mac.
12
+
13
+ ### Changed
14
+
15
+ - Added an optional `ollama` dependency group while keeping the core package
16
+ dependency-free.
17
+ - Expanded the README, Cookbook, web Manual, and agent-facing navigation with
18
+ a complete local-model request path and measured memory/latency trade-offs.
19
+
3
20
  ## 0.2.2 - 2026-07-15
4
21
 
5
22
  Azure OpenAI adoption release.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pyveil
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Local PII and secret redaction for Python LLM apps, AI agents, tool calls, and MCP
5
5
  Author-email: Hyeon Sang Jeon <wingnut0310@gmail.com>
6
6
  Maintainer-email: Hyeon Sang Jeon <wingnut0310@gmail.com>
@@ -39,6 +39,10 @@ Provides-Extra: azure-openai
39
39
  Requires-Dist: openai>=1.66.0; extra == "azure-openai"
40
40
  Requires-Dist: python-dotenv>=1.0.0; extra == "azure-openai"
41
41
  Requires-Dist: PyYAML>=6.0.0; extra == "azure-openai"
42
+ Provides-Extra: ollama
43
+ Requires-Dist: ollama<1.0.0,>=0.6.2; extra == "ollama"
44
+ Requires-Dist: python-dotenv>=1.0.0; extra == "ollama"
45
+ Requires-Dist: PyYAML>=6.0.0; extra == "ollama"
42
46
  Provides-Extra: dev
43
47
  Requires-Dist: build>=1.2.0; extra == "dev"
44
48
  Requires-Dist: pytest>=7.0.0; extra == "dev"
@@ -47,6 +51,7 @@ Requires-Dist: black>=23.0.0; extra == "dev"
47
51
  Requires-Dist: ruff>=0.1.0; extra == "dev"
48
52
  Requires-Dist: twine>=5.0.0; extra == "dev"
49
53
  Requires-Dist: mypy>=1.0.0; extra == "dev"
54
+ Requires-Dist: ollama<1.0.0,>=0.6.2; extra == "dev"
50
55
  Requires-Dist: openai>=1.66.0; extra == "dev"
51
56
  Requires-Dist: python-dotenv>=1.0.0; extra == "dev"
52
57
  Requires-Dist: PyYAML>=6.0.0; extra == "dev"
@@ -140,6 +145,70 @@ response = call_llm(safe.data) # Your provider SDK call
140
145
  The provider receives the same list and dictionary shape, with sensitive values
141
146
  replaced before serialization or transmission.
142
147
 
148
+ ## Ollama: Local End To End
149
+
150
+ Run a local model behind the same redaction boundary. The optional integration
151
+ uses Ollama's official Python client and defaults to `qwen3.5:4b`, a Q4_K_M
152
+ 4.7B model that fits comfortably on a 16GB Apple silicon Mac with a 4K context:
153
+
154
+ ```bash
155
+ pip install "pyveil[ollama]"
156
+ ollama pull qwen3.5:4b
157
+ ```
158
+
159
+ ```python
160
+ from pyveil.integrations.ollama import ask_ollama, load_settings
161
+
162
+ settings = load_settings() # OLLAMA_* + PYVEIL_* environment variables
163
+ result = ask_ollama(
164
+ "Write a follow-up for alice@example.com or 010-1234-5678.",
165
+ settings,
166
+ )
167
+
168
+ print(result.redacted_input) # The exact prompt sent to Ollama
169
+ print(result.output_text) # The local model response
170
+ ```
171
+
172
+ Prove the boundary without loading a model:
173
+
174
+ ```bash
175
+ PYVEIL_SECRET=docs-demo-secret \
176
+ python -m pyveil.integrations.ollama --dry-run
177
+ ```
178
+
179
+ ```text
180
+ mode: dry-run
181
+ model: qwen3.5:4b
182
+ host: http://127.0.0.1:11434
183
+ sent-to-ollama: Write a one-sentence support follow-up for [EMAIL:71c6727a7fa2] or [PHONE:b4b889df07ce].
184
+ findings: EMAIL=1, PHONE=1
185
+ ollama-response: skipped (--dry-run)
186
+ ```
187
+
188
+ For a live local call, set `PYVEIL_SECRET` and run the module. Configuration
189
+ priority is process environment, `.env`, YAML, then defaults:
190
+
191
+ ```bash
192
+ PYVEIL_SECRET=a-long-random-hmac-secret \
193
+ python -m pyveil.integrations.ollama
194
+
195
+ python -m pyveil.integrations.ollama \
196
+ --config examples/ollama.example.yaml --env-file .env
197
+ ```
198
+
199
+ The checked-in [`.env` template](examples/ollama.env.example) and
200
+ [YAML template](examples/ollama.example.yaml) expose model, host, context,
201
+ output length, temperature, timeout, and keep-alive. pyveil caps the default
202
+ context at 4096 and uses `keep_alive=0`, so one-shot calls release model memory;
203
+ set `OLLAMA_KEEP_ALIVE=5m` for faster repeated calls.
204
+
205
+ Observed on this project's M1 Mac mini with 16GB memory and Ollama 0.31.2:
206
+ `qwen3.5:4b` used about 3.2GB at a 4096-token context, a cold request took
207
+ 8.1 seconds, and a warm request took 1.3 seconds. These are local measurements,
208
+ not portable performance guarantees. See the
209
+ [Ollama integration guide](docs/integrations/ollama.md) for the full setup and
210
+ memory trade-offs.
211
+
143
212
  ## Azure OpenAI: End To End
144
213
 
145
214
  Install the optional Azure example dependencies, then load configuration from
@@ -226,7 +295,7 @@ evaluator:
226
295
  python evaluation/evaluate.py --check
227
296
  ```
228
297
 
229
- For corpus v1, pyveil 0.2.2 matches all 36 expected findings across 39 cases
298
+ For corpus v1, pyveil 0.2.3 matches all 36 expected findings across 39 cases
230
299
  (33 positive, 6 negative), with no corpus false positives, false negatives,
231
300
  labeled-value leaks, or non-empty `Finding.raw` values.
232
301
 
@@ -81,6 +81,70 @@ response = call_llm(safe.data) # Your provider SDK call
81
81
  The provider receives the same list and dictionary shape, with sensitive values
82
82
  replaced before serialization or transmission.
83
83
 
84
+ ## Ollama: Local End To End
85
+
86
+ Run a local model behind the same redaction boundary. The optional integration
87
+ uses Ollama's official Python client and defaults to `qwen3.5:4b`, a Q4_K_M
88
+ 4.7B model that fits comfortably on a 16GB Apple silicon Mac with a 4K context:
89
+
90
+ ```bash
91
+ pip install "pyveil[ollama]"
92
+ ollama pull qwen3.5:4b
93
+ ```
94
+
95
+ ```python
96
+ from pyveil.integrations.ollama import ask_ollama, load_settings
97
+
98
+ settings = load_settings() # OLLAMA_* + PYVEIL_* environment variables
99
+ result = ask_ollama(
100
+ "Write a follow-up for alice@example.com or 010-1234-5678.",
101
+ settings,
102
+ )
103
+
104
+ print(result.redacted_input) # The exact prompt sent to Ollama
105
+ print(result.output_text) # The local model response
106
+ ```
107
+
108
+ Prove the boundary without loading a model:
109
+
110
+ ```bash
111
+ PYVEIL_SECRET=docs-demo-secret \
112
+ python -m pyveil.integrations.ollama --dry-run
113
+ ```
114
+
115
+ ```text
116
+ mode: dry-run
117
+ model: qwen3.5:4b
118
+ host: http://127.0.0.1:11434
119
+ sent-to-ollama: Write a one-sentence support follow-up for [EMAIL:71c6727a7fa2] or [PHONE:b4b889df07ce].
120
+ findings: EMAIL=1, PHONE=1
121
+ ollama-response: skipped (--dry-run)
122
+ ```
123
+
124
+ For a live local call, set `PYVEIL_SECRET` and run the module. Configuration
125
+ priority is process environment, `.env`, YAML, then defaults:
126
+
127
+ ```bash
128
+ PYVEIL_SECRET=a-long-random-hmac-secret \
129
+ python -m pyveil.integrations.ollama
130
+
131
+ python -m pyveil.integrations.ollama \
132
+ --config examples/ollama.example.yaml --env-file .env
133
+ ```
134
+
135
+ The checked-in [`.env` template](examples/ollama.env.example) and
136
+ [YAML template](examples/ollama.example.yaml) expose model, host, context,
137
+ output length, temperature, timeout, and keep-alive. pyveil caps the default
138
+ context at 4096 and uses `keep_alive=0`, so one-shot calls release model memory;
139
+ set `OLLAMA_KEEP_ALIVE=5m` for faster repeated calls.
140
+
141
+ Observed on this project's M1 Mac mini with 16GB memory and Ollama 0.31.2:
142
+ `qwen3.5:4b` used about 3.2GB at a 4096-token context, a cold request took
143
+ 8.1 seconds, and a warm request took 1.3 seconds. These are local measurements,
144
+ not portable performance guarantees. See the
145
+ [Ollama integration guide](docs/integrations/ollama.md) for the full setup and
146
+ memory trade-offs.
147
+
84
148
  ## Azure OpenAI: End To End
85
149
 
86
150
  Install the optional Azure example dependencies, then load configuration from
@@ -167,7 +231,7 @@ evaluator:
167
231
  python evaluation/evaluate.py --check
168
232
  ```
169
233
 
170
- For corpus v1, pyveil 0.2.2 matches all 36 expected findings across 39 cases
234
+ For corpus v1, pyveil 0.2.3 matches all 36 expected findings across 39 cases
171
235
  (33 positive, 6 negative), with no corpus false positives, false negatives,
172
236
  labeled-value leaks, or non-empty `Finding.raw` values.
173
237
 
@@ -12,6 +12,7 @@ Start with:
12
12
  - [Threat model](threat-model.md)
13
13
  - [FAQ](faq.md)
14
14
  - [Cookbook](cookbook.md)
15
+ - [Ollama local-model integration](integrations/ollama.md)
15
16
  - [Azure OpenAI env/YAML integration](../pyveil/integrations/azure_openai.py)
16
17
  - [Redaction reference](redaction-reference.md)
17
18
  - [Known limitations](known-limitations.md)
@@ -48,7 +48,92 @@ response = call_with_redaction(
48
48
  print(response)
49
49
  ```
50
50
 
51
- ## 2. Azure OpenAI With Environment Or YAML Configuration
51
+ ## 2. Ollama With A Memory-Aware Local Model
52
+
53
+ Install the optional dependencies and pull the recommended 16GB-Mac model:
54
+
55
+ ```bash
56
+ pip install "pyveil[ollama]"
57
+ ollama pull qwen3.5:4b
58
+ ```
59
+
60
+ The helper redacts before `Client.chat(...)` and returns the exact local-model
61
+ input, response text, and available timing/token metrics:
62
+
63
+ ```python
64
+ from pyveil.integrations.ollama import ask_ollama, load_settings
65
+
66
+
67
+ def answer_with_ollama(prompt: str) -> str:
68
+ settings = load_settings()
69
+ result = ask_ollama(prompt, settings)
70
+ print("sent-to-ollama:", result.redacted_input)
71
+ print("total-ms:", result.total_duration_ms)
72
+ return result.output_text or ""
73
+
74
+
75
+ answer = answer_with_ollama(
76
+ "Write a follow-up for alice@example.com or 010-1234-5678."
77
+ )
78
+ ```
79
+
80
+ Under the hood, the boundary stays deliberately small:
81
+
82
+ ```python
83
+ from ollama import Client
84
+ from pyveil import Channel, Veil
85
+
86
+ client = Client(host=settings.host, timeout=settings.timeout_seconds)
87
+ veil = Veil.high(
88
+ secret=settings.pyveil_secret,
89
+ scope=settings.pyveil_scope,
90
+ )
91
+
92
+ safe = veil.redact_text(prompt, channel=Channel.PROMPT_INPUT)
93
+ response = client.chat(
94
+ model=settings.model,
95
+ messages=[{"role": "user", "content": safe.text}],
96
+ stream=False,
97
+ think=False,
98
+ options={"num_ctx": 4096, "num_predict": 128, "temperature": 0.2},
99
+ keep_alive="0",
100
+ )
101
+ ```
102
+
103
+ Try the non-model and live paths:
104
+
105
+ ```bash
106
+ export PYVEIL_SECRET="a-long-random-hmac-secret"
107
+
108
+ python -m pyveil.integrations.ollama --dry-run
109
+ python -m pyveil.integrations.ollama
110
+ python -m pyveil.integrations.ollama \
111
+ --config examples/ollama.example.yaml --env-file .env
112
+ ```
113
+
114
+ Configuration priority is process environment, `.env`, YAML, then defaults.
115
+ The default `qwen3.5:4b` configuration uses a 4096-token context and
116
+ `keep_alive=0` to release memory after each response. Use
117
+ `OLLAMA_KEEP_ALIVE=5m` when repeated-call latency matters more than reclaiming
118
+ roughly 3.2GB immediately on a 16GB Apple silicon Mac.
119
+
120
+ Dry-run output from the synthetic default prompt:
121
+
122
+ ```text
123
+ mode: dry-run
124
+ model: qwen3.5:4b
125
+ host: http://127.0.0.1:11434
126
+ sent-to-ollama: Write a one-sentence support follow-up for [EMAIL:71c6727a7fa2] or [PHONE:b4b889df07ce].
127
+ findings: EMAIL=1, PHONE=1
128
+ ollama-response: skipped (--dry-run)
129
+ ```
130
+
131
+ See [`pyveil/integrations/ollama.py`](../pyveil/integrations/ollama.py),
132
+ [`examples/ollama.env.example`](../examples/ollama.env.example),
133
+ [`examples/ollama.example.yaml`](../examples/ollama.example.yaml), and the
134
+ [full Ollama integration guide](integrations/ollama.md).
135
+
136
+ ## 3. Azure OpenAI With Environment Or YAML Configuration
52
137
 
53
138
  Install the optional dependencies:
54
139
 
@@ -137,7 +222,7 @@ See [`pyveil/integrations/azure_openai.py`](../pyveil/integrations/azure_openai.
137
222
  [`examples/azure_openai.env.example`](../examples/azure_openai.env.example), and
138
223
  [`examples/azure_openai.example.yaml`](../examples/azure_openai.example.yaml).
139
224
 
140
- ## 3. Block Credentials Before Model-Controlled Tool Calls
225
+ ## 4. Block Credentials Before Model-Controlled Tool Calls
141
226
 
142
227
  The default policy blocks credential-like material in `tool.call.arguments`.
143
228
 
@@ -160,7 +245,7 @@ else:
160
245
  run_tool(**safe_args)
161
246
  ```
162
247
 
163
- ## 4. Redact Tool Results Before Returning Them To The Model
248
+ ## 5. Redact Tool Results Before Returning Them To The Model
164
249
 
165
250
  Tool results often contain customer records, logs, URLs, headers, or hidden tokens.
166
251
 
@@ -177,7 +262,7 @@ raw_result = {
177
262
  safe_result = veil.redact_data(raw_result, channel=Channel.TOOL_CALL_RESULT).data
178
263
  ```
179
264
 
180
- ## 5. Redact MCP Resource Content
265
+ ## 6. Redact MCP Resource Content
181
266
 
182
267
  MCP resources can expose files, database rows, logs, or API payloads. Redact before the resource content enters an agent context.
183
268
 
@@ -192,7 +277,7 @@ def read_resource_for_agent(resource_id: str) -> object:
192
277
  return veil.redact_data(raw_resource, channel=Channel.MCP_RESOURCE_CONTENT).data
193
278
  ```
194
279
 
195
- ## 6. Redact Memory Before Embedding Or Persistence
280
+ ## 7. Redact Memory Before Embedding Or Persistence
196
281
 
197
282
  Memory stores create long-lived recall. Redact before embedding and before persistence.
198
283
 
@@ -208,7 +293,7 @@ def write_memory(text: str) -> None:
208
293
  memory_store.save(text=safe.text, embedding=embedding)
209
294
  ```
210
295
 
211
- ## 7. Redact Logs Before Export
296
+ ## 8. Redact Logs Before Export
212
297
 
213
298
  Use the built-in logging integration for application logs.
214
299
 
@@ -224,7 +309,7 @@ logger.addFilter(PyVeilLogFilter(Veil.high(secret=b"log-secret", scope="prod/log
224
309
  logger.warning("failed request for alice@example.com")
225
310
  ```
226
311
 
227
- ## 8. CLI Preflight In CI Or Local Scripts
312
+ ## 9. CLI Preflight In CI Or Local Scripts
228
313
 
229
314
  Use `pyveil scan` to count findings and `pyveil redact` to create a redacted artifact.
230
315
 
@@ -239,7 +324,7 @@ pyveil redact prompt.txt --channel prompt.input > prompt.safe.txt
239
324
  pyveil redact request.json --channel tool.call.result --format json > request.safe.json
240
325
  ```
241
326
 
242
- ## 9. Redact Known Names And Domain Identifiers
327
+ ## 10. Redact Known Names And Domain Identifiers
243
328
 
244
329
  The dependency-free core does not guess unknown names. Add values that your
245
330
  application already knows are sensitive, plus narrow identifiers from your own
@@ -47,7 +47,7 @@
47
47
  <p class="eyebrow">Evidence, not promises</p>
48
48
  <h1>A reproducible detector <strong>regression evaluation</strong>.</h1>
49
49
  <p class="lede">The public corpus tests documented supported shapes, negative examples, labeled-value removal, and the no-raw-findings contract on every change.</p>
50
- <p class="meta">Corpus v1 &middot; pyveil 0.2.2 &middot; Last run July 15, 2026</p>
50
+ <p class="meta">Corpus v1 &middot; pyveil 0.2.3 &middot; Last run July 16, 2026</p>
51
51
  </div>
52
52
  </div>
53
53
 
@@ -27,7 +27,7 @@
27
27
  "name": "pyveil",
28
28
  "applicationCategory": "DeveloperApplication",
29
29
  "operatingSystem": "Any",
30
- "softwareVersion": "0.2.2",
30
+ "softwareVersion": "0.2.3",
31
31
  "programmingLanguage": "Python",
32
32
  "description": "Local PII and secret redaction for Python LLM apps and AI agents.",
33
33
  "url": "https://hyeonsangjeon.github.io/pyveil/",
@@ -386,6 +386,11 @@ API key [API_KEY:38ded98a17e7]</code></pre>
386
386
  <section class="section">
387
387
  <h2>Recipes people actually need</h2>
388
388
  <div class="grid">
389
+ <a class="recipe" href="manual.html#ollama">
390
+ <h3>Ollama, local and memory-aware</h3>
391
+ <p>Run a tested 4B model on a 16GB Mac and inspect the exact redacted prompt before local inference.</p>
392
+ <span class="more">Read &rarr;</span>
393
+ </a>
389
394
  <a class="recipe" href="manual.html#azure-openai">
390
395
  <h3>Azure OpenAI, end to end</h3>
391
396
  <p>Load env or YAML, inspect the exact redacted prompt, then call the Azure v1 Responses API.</p>
@@ -0,0 +1,134 @@
1
+ # Ollama Integration
2
+
3
+ Use `pyveil.integrations.ollama` when a prompt should be redacted immediately
4
+ before it enters a local Ollama model.
5
+
6
+ ```text
7
+ raw prompt -> pyveil HIGH redaction -> Ollama Client.chat -> local model
8
+ ```
9
+
10
+ ## Install
11
+
12
+ Install the optional official client and configuration dependencies, then pull
13
+ a model:
14
+
15
+ ```bash
16
+ pip install "pyveil[ollama]"
17
+ ollama pull qwen3.5:4b
18
+ ```
19
+
20
+ The integration does not pull a model automatically. Model downloads are large
21
+ and should remain an explicit operator decision.
22
+
23
+ ## Why `qwen3.5:4b` For A 16GB Mac
24
+
25
+ The development machine used for this integration is an M1 Mac mini with 16GB
26
+ of unified memory. The selected `qwen3.5:4b` tag is a Q4_K_M model with about
27
+ 4.7B parameters and a 3.4GB download. The integration limits context to 4096
28
+ tokens even though the model supports more, because KV cache grows with context.
29
+
30
+ Observed locally with Ollama 0.31.2:
31
+
32
+ | Check | Observed result |
33
+ | --- | --- |
34
+ | Loaded model size from `ollama ps` | 3.2GB |
35
+ | Processor | 100% GPU |
36
+ | Context | 4096 tokens |
37
+ | Cold live request | 8.1s total, 5.2s model load |
38
+ | Warm live request | 1.3s total, 0.25s load |
39
+ | Memory free indicator while resident | 26% |
40
+ | Memory free indicator after unload | 57% |
41
+
42
+ These values are a single-machine smoke test, not a benchmark. Background
43
+ applications, prompt length, model updates, and Ollama versions will change
44
+ them.
45
+
46
+ ## Dry Run
47
+
48
+ Dry-run proves what would cross the client boundary without loading the model:
49
+
50
+ ```bash
51
+ PYVEIL_SECRET=docs-demo-secret \
52
+ python -m pyveil.integrations.ollama --dry-run
53
+ ```
54
+
55
+ ```text
56
+ mode: dry-run
57
+ model: qwen3.5:4b
58
+ host: http://127.0.0.1:11434
59
+ sent-to-ollama: Write a one-sentence support follow-up for [EMAIL:71c6727a7fa2] or [PHONE:b4b889df07ce].
60
+ findings: EMAIL=1, PHONE=1
61
+ ollama-response: skipped (--dry-run)
62
+ ```
63
+
64
+ ## Live Call
65
+
66
+ ```bash
67
+ export PYVEIL_SECRET="a-long-random-hmac-secret"
68
+ python -m pyveil.integrations.ollama
69
+ ```
70
+
71
+ Or call the reusable API:
72
+
73
+ ```python
74
+ from pyveil.integrations.ollama import ask_ollama, load_settings
75
+
76
+ settings = load_settings()
77
+ result = ask_ollama(
78
+ "Write a support reply for alice@example.com or 010-1234-5678.",
79
+ settings,
80
+ )
81
+
82
+ print(result.redacted_input)
83
+ print(result.output_text)
84
+ print(result.total_duration_ms, result.prompt_eval_count, result.eval_count)
85
+ ```
86
+
87
+ `redacted_input` is the exact value passed to `Client.chat`. Findings and
88
+ exceptions do not retain the original email or phone number.
89
+
90
+ ## Configuration
91
+
92
+ Priority is process environment, `.env`, YAML, then defaults.
93
+
94
+ | Environment | YAML key | Default | Purpose |
95
+ | --- | --- | --- | --- |
96
+ | `OLLAMA_HOST` | `ollama.host` | `http://127.0.0.1:11434` | Ollama server URL |
97
+ | `OLLAMA_MODEL` | `ollama.model` | `qwen3.5:4b` | Local model tag |
98
+ | `OLLAMA_NUM_CTX` | `ollama.num_ctx` | `4096` | Context and KV-cache limit |
99
+ | `OLLAMA_NUM_PREDICT` | `ollama.num_predict` | `128` | Maximum output tokens |
100
+ | `OLLAMA_TEMPERATURE` | `ollama.temperature` | `0.2` | Sampling temperature |
101
+ | `OLLAMA_KEEP_ALIVE` | `ollama.keep_alive` | `0` | How long the model stays resident |
102
+ | `OLLAMA_TIMEOUT` | `ollama.timeout_seconds` | `120` | HTTP timeout in seconds |
103
+ | `PYVEIL_SECRET` | `pyveil.secret_env` | required | HMAC secret environment variable |
104
+ | `PYVEIL_SCOPE` | `pyveil.scope` | `ollama/example` | Placeholder namespace |
105
+
106
+ ```bash
107
+ python -m pyveil.integrations.ollama \
108
+ --config examples/ollama.example.yaml \
109
+ --env-file .env
110
+ ```
111
+
112
+ The pyveil secret cannot be stored directly in YAML. The loader rejects a
113
+ `pyveil.secret` field; YAML may only name the environment variable that holds
114
+ it.
115
+
116
+ ## Memory And Latency
117
+
118
+ - Keep the default `OLLAMA_KEEP_ALIVE=0` for one-shot jobs or a memory-busy
119
+ 16GB machine. Ollama unloads the model after the response.
120
+ - Set `OLLAMA_KEEP_ALIVE=5m` for repeated agent turns. Warm requests are much
121
+ faster, but the model remains resident during that window.
122
+ - Keep `OLLAMA_NUM_CTX=4096` unless the application proves it needs more.
123
+ - Inspect current residency with `ollama ps`; unload manually with
124
+ `ollama stop qwen3.5:4b`.
125
+
126
+ ## Boundary Notes
127
+
128
+ - `127.0.0.1` keeps the redacted request on the machine. A remote
129
+ `OLLAMA_HOST` creates a network boundary even though the raw PII was removed.
130
+ - pyveil protects the input boundary. If model output will be logged, stored,
131
+ or sent onward, redact it again with channel `prompt.output`.
132
+ - Regex and known-value rules do not discover arbitrary names or addresses.
133
+ - The integration disables thinking output and returns only
134
+ `message.content`; it does not persist or expose a reasoning trace.
@@ -43,6 +43,7 @@ EMAIL, PHONE, CREDIT_CARD, JWT, AUTH_HEADER, PRIVATE_KEY, API_KEY, URL_QUERY_SEC
43
43
  ## Repository documents
44
44
 
45
45
  - Cookbook: https://github.com/hyeonsangjeon/pyveil/blob/main/docs/cookbook.md
46
+ - Ollama integration: https://github.com/hyeonsangjeon/pyveil/blob/main/docs/integrations/ollama.md
46
47
  - Redaction reference: https://github.com/hyeonsangjeon/pyveil/blob/main/docs/redaction-reference.md
47
48
  - Threat model: https://github.com/hyeonsangjeon/pyveil/blob/main/docs/threat-model.md
48
49
  - Known limitations: https://github.com/hyeonsangjeon/pyveil/blob/main/docs/known-limitations.md