posthog 7.5.1__py3-none-any.whl → 7.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- posthog/__init__.py +35 -0
- posthog/ai/openai_agents/__init__.py +76 -0
- posthog/ai/openai_agents/processor.py +863 -0
- posthog/client.py +38 -0
- posthog/contexts.py +44 -0
- posthog/test/ai/__init__.py +0 -0
- posthog/test/ai/openai_agents/__init__.py +1 -0
- posthog/test/ai/openai_agents/test_processor.py +810 -0
- posthog/test/ai/test_sanitization.py +522 -0
- posthog/test/ai/test_system_prompts.py +363 -0
- posthog/test/test_client.py +115 -0
- posthog/version.py +1 -1
- {posthog-7.5.1.dist-info → posthog-7.7.0.dist-info}/METADATA +1 -1
- {posthog-7.5.1.dist-info → posthog-7.7.0.dist-info}/RECORD +17 -10
- {posthog-7.5.1.dist-info → posthog-7.7.0.dist-info}/WHEEL +0 -0
- {posthog-7.5.1.dist-info → posthog-7.7.0.dist-info}/licenses/LICENSE +0 -0
- {posthog-7.5.1.dist-info → posthog-7.7.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
from posthog.ai.sanitization import (
|
|
5
|
+
redact_base64_data_url,
|
|
6
|
+
sanitize_openai,
|
|
7
|
+
sanitize_openai_response,
|
|
8
|
+
sanitize_anthropic,
|
|
9
|
+
sanitize_gemini,
|
|
10
|
+
sanitize_langchain,
|
|
11
|
+
is_base64_data_url,
|
|
12
|
+
is_raw_base64,
|
|
13
|
+
REDACTED_IMAGE_PLACEHOLDER,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestSanitization(unittest.TestCase):
|
|
18
|
+
def setUp(self):
|
|
19
|
+
self.sample_base64_image = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..."
|
|
20
|
+
self.sample_base64_png = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA..."
|
|
21
|
+
self.regular_url = "https://example.com/image.jpg"
|
|
22
|
+
self.raw_base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUl=="
|
|
23
|
+
|
|
24
|
+
def test_is_base64_data_url(self):
|
|
25
|
+
self.assertTrue(is_base64_data_url(self.sample_base64_image))
|
|
26
|
+
self.assertTrue(is_base64_data_url(self.sample_base64_png))
|
|
27
|
+
self.assertFalse(is_base64_data_url(self.regular_url))
|
|
28
|
+
self.assertFalse(is_base64_data_url("regular text"))
|
|
29
|
+
|
|
30
|
+
def test_is_raw_base64(self):
|
|
31
|
+
self.assertTrue(is_raw_base64(self.raw_base64))
|
|
32
|
+
self.assertFalse(is_raw_base64("short"))
|
|
33
|
+
self.assertFalse(is_raw_base64(self.regular_url))
|
|
34
|
+
self.assertFalse(is_raw_base64("/path/to/file"))
|
|
35
|
+
|
|
36
|
+
def test_redact_base64_data_url(self):
|
|
37
|
+
self.assertEqual(
|
|
38
|
+
redact_base64_data_url(self.sample_base64_image), REDACTED_IMAGE_PLACEHOLDER
|
|
39
|
+
)
|
|
40
|
+
self.assertEqual(
|
|
41
|
+
redact_base64_data_url(self.sample_base64_png), REDACTED_IMAGE_PLACEHOLDER
|
|
42
|
+
)
|
|
43
|
+
self.assertEqual(redact_base64_data_url(self.regular_url), self.regular_url)
|
|
44
|
+
self.assertEqual(redact_base64_data_url(None), None)
|
|
45
|
+
self.assertEqual(redact_base64_data_url(123), 123)
|
|
46
|
+
|
|
47
|
+
def test_sanitize_openai(self):
|
|
48
|
+
input_data = [
|
|
49
|
+
{
|
|
50
|
+
"role": "user",
|
|
51
|
+
"content": [
|
|
52
|
+
{"type": "text", "text": "What is in this image?"},
|
|
53
|
+
{
|
|
54
|
+
"type": "image_url",
|
|
55
|
+
"image_url": {
|
|
56
|
+
"url": self.sample_base64_image,
|
|
57
|
+
"detail": "high",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
result = sanitize_openai(input_data)
|
|
65
|
+
|
|
66
|
+
self.assertEqual(result[0]["content"][0]["text"], "What is in this image?")
|
|
67
|
+
self.assertEqual(
|
|
68
|
+
result[0]["content"][1]["image_url"]["url"], REDACTED_IMAGE_PLACEHOLDER
|
|
69
|
+
)
|
|
70
|
+
self.assertEqual(result[0]["content"][1]["image_url"]["detail"], "high")
|
|
71
|
+
|
|
72
|
+
def test_sanitize_openai_preserves_regular_urls(self):
|
|
73
|
+
input_data = [
|
|
74
|
+
{
|
|
75
|
+
"role": "user",
|
|
76
|
+
"content": [
|
|
77
|
+
{
|
|
78
|
+
"type": "image_url",
|
|
79
|
+
"image_url": {"url": self.regular_url},
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
result = sanitize_openai(input_data)
|
|
86
|
+
self.assertEqual(result[0]["content"][0]["image_url"]["url"], self.regular_url)
|
|
87
|
+
|
|
88
|
+
def test_sanitize_openai_response(self):
|
|
89
|
+
input_data = [
|
|
90
|
+
{
|
|
91
|
+
"role": "user",
|
|
92
|
+
"content": [
|
|
93
|
+
{
|
|
94
|
+
"type": "input_image",
|
|
95
|
+
"image_url": self.sample_base64_image,
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
result = sanitize_openai_response(input_data)
|
|
102
|
+
self.assertEqual(
|
|
103
|
+
result[0]["content"][0]["image_url"], REDACTED_IMAGE_PLACEHOLDER
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def test_sanitize_anthropic(self):
|
|
107
|
+
input_data = [
|
|
108
|
+
{
|
|
109
|
+
"role": "user",
|
|
110
|
+
"content": [
|
|
111
|
+
{"type": "text", "text": "What is in this image?"},
|
|
112
|
+
{
|
|
113
|
+
"type": "image",
|
|
114
|
+
"source": {
|
|
115
|
+
"type": "base64",
|
|
116
|
+
"media_type": "image/jpeg",
|
|
117
|
+
"data": "base64data",
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
result = sanitize_anthropic(input_data)
|
|
125
|
+
|
|
126
|
+
self.assertEqual(result[0]["content"][0]["text"], "What is in this image?")
|
|
127
|
+
self.assertEqual(
|
|
128
|
+
result[0]["content"][1]["source"]["data"], REDACTED_IMAGE_PLACEHOLDER
|
|
129
|
+
)
|
|
130
|
+
self.assertEqual(result[0]["content"][1]["source"]["type"], "base64")
|
|
131
|
+
self.assertEqual(result[0]["content"][1]["source"]["media_type"], "image/jpeg")
|
|
132
|
+
|
|
133
|
+
def test_sanitize_gemini(self):
|
|
134
|
+
input_data = [
|
|
135
|
+
{
|
|
136
|
+
"parts": [
|
|
137
|
+
{"text": "What is in this image?"},
|
|
138
|
+
{
|
|
139
|
+
"inline_data": {
|
|
140
|
+
"mime_type": "image/jpeg",
|
|
141
|
+
"data": "base64data",
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
result = sanitize_gemini(input_data)
|
|
149
|
+
|
|
150
|
+
self.assertEqual(result[0]["parts"][0]["text"], "What is in this image?")
|
|
151
|
+
self.assertEqual(
|
|
152
|
+
result[0]["parts"][1]["inline_data"]["data"], REDACTED_IMAGE_PLACEHOLDER
|
|
153
|
+
)
|
|
154
|
+
self.assertEqual(
|
|
155
|
+
result[0]["parts"][1]["inline_data"]["mime_type"], "image/jpeg"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def test_sanitize_langchain_openai_style(self):
|
|
159
|
+
input_data = [
|
|
160
|
+
{
|
|
161
|
+
"role": "user",
|
|
162
|
+
"content": [
|
|
163
|
+
{
|
|
164
|
+
"type": "image_url",
|
|
165
|
+
"image_url": {"url": self.sample_base64_image},
|
|
166
|
+
}
|
|
167
|
+
],
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
result = sanitize_langchain(input_data)
|
|
172
|
+
self.assertEqual(
|
|
173
|
+
result[0]["content"][0]["image_url"]["url"], REDACTED_IMAGE_PLACEHOLDER
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def test_sanitize_langchain_anthropic_style(self):
|
|
177
|
+
input_data = [
|
|
178
|
+
{
|
|
179
|
+
"role": "user",
|
|
180
|
+
"content": [
|
|
181
|
+
{
|
|
182
|
+
"type": "image",
|
|
183
|
+
"source": {"data": "base64data"},
|
|
184
|
+
}
|
|
185
|
+
],
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
result = sanitize_langchain(input_data)
|
|
190
|
+
self.assertEqual(
|
|
191
|
+
result[0]["content"][0]["source"]["data"], REDACTED_IMAGE_PLACEHOLDER
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def test_sanitize_with_data_url_format(self):
|
|
195
|
+
# Test that data URLs are properly detected and redacted across providers
|
|
196
|
+
data_url = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD"
|
|
197
|
+
|
|
198
|
+
# OpenAI format
|
|
199
|
+
openai_data = [
|
|
200
|
+
{
|
|
201
|
+
"role": "user",
|
|
202
|
+
"content": [{"type": "image_url", "image_url": {"url": data_url}}],
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
result = sanitize_openai(openai_data)
|
|
206
|
+
self.assertEqual(
|
|
207
|
+
result[0]["content"][0]["image_url"]["url"], REDACTED_IMAGE_PLACEHOLDER
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Anthropic format
|
|
211
|
+
anthropic_data = [
|
|
212
|
+
{
|
|
213
|
+
"role": "user",
|
|
214
|
+
"content": [
|
|
215
|
+
{
|
|
216
|
+
"type": "image",
|
|
217
|
+
"source": {
|
|
218
|
+
"type": "base64",
|
|
219
|
+
"media_type": "image/jpeg",
|
|
220
|
+
"data": data_url,
|
|
221
|
+
},
|
|
222
|
+
}
|
|
223
|
+
],
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
result = sanitize_anthropic(anthropic_data)
|
|
227
|
+
self.assertEqual(
|
|
228
|
+
result[0]["content"][0]["source"]["data"], REDACTED_IMAGE_PLACEHOLDER
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# LangChain format
|
|
232
|
+
langchain_data = [
|
|
233
|
+
{"role": "user", "content": [{"type": "image", "data": data_url}]}
|
|
234
|
+
]
|
|
235
|
+
result = sanitize_langchain(langchain_data)
|
|
236
|
+
self.assertEqual(result[0]["content"][0]["data"], REDACTED_IMAGE_PLACEHOLDER)
|
|
237
|
+
|
|
238
|
+
def test_sanitize_with_raw_base64(self):
|
|
239
|
+
# Test that raw base64 strings (without data URL prefix) are detected
|
|
240
|
+
raw_base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUl=="
|
|
241
|
+
|
|
242
|
+
# Test with Anthropic format
|
|
243
|
+
anthropic_data = [
|
|
244
|
+
{
|
|
245
|
+
"role": "user",
|
|
246
|
+
"content": [
|
|
247
|
+
{
|
|
248
|
+
"type": "image",
|
|
249
|
+
"source": {
|
|
250
|
+
"type": "base64",
|
|
251
|
+
"media_type": "image/png",
|
|
252
|
+
"data": raw_base64,
|
|
253
|
+
},
|
|
254
|
+
}
|
|
255
|
+
],
|
|
256
|
+
}
|
|
257
|
+
]
|
|
258
|
+
result = sanitize_anthropic(anthropic_data)
|
|
259
|
+
self.assertEqual(
|
|
260
|
+
result[0]["content"][0]["source"]["data"], REDACTED_IMAGE_PLACEHOLDER
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Test with Gemini format
|
|
264
|
+
gemini_data = [
|
|
265
|
+
{"parts": [{"inline_data": {"mime_type": "image/png", "data": raw_base64}}]}
|
|
266
|
+
]
|
|
267
|
+
result = sanitize_gemini(gemini_data)
|
|
268
|
+
self.assertEqual(
|
|
269
|
+
result[0]["parts"][0]["inline_data"]["data"], REDACTED_IMAGE_PLACEHOLDER
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
def test_sanitize_preserves_regular_content(self):
|
|
273
|
+
# Ensure non-base64 content is preserved across all providers
|
|
274
|
+
regular_url = "https://example.com/image.jpg"
|
|
275
|
+
text_content = "What do you see?"
|
|
276
|
+
|
|
277
|
+
# OpenAI
|
|
278
|
+
openai_data = [
|
|
279
|
+
{
|
|
280
|
+
"role": "user",
|
|
281
|
+
"content": [
|
|
282
|
+
{"type": "text", "text": text_content},
|
|
283
|
+
{"type": "image_url", "image_url": {"url": regular_url}},
|
|
284
|
+
],
|
|
285
|
+
}
|
|
286
|
+
]
|
|
287
|
+
result = sanitize_openai(openai_data)
|
|
288
|
+
self.assertEqual(result[0]["content"][0]["text"], text_content)
|
|
289
|
+
self.assertEqual(result[0]["content"][1]["image_url"]["url"], regular_url)
|
|
290
|
+
|
|
291
|
+
# Anthropic
|
|
292
|
+
anthropic_data = [
|
|
293
|
+
{
|
|
294
|
+
"role": "user",
|
|
295
|
+
"content": [
|
|
296
|
+
{"type": "text", "text": text_content},
|
|
297
|
+
{"type": "image", "source": {"type": "url", "url": regular_url}},
|
|
298
|
+
],
|
|
299
|
+
}
|
|
300
|
+
]
|
|
301
|
+
result = sanitize_anthropic(anthropic_data)
|
|
302
|
+
self.assertEqual(result[0]["content"][0]["text"], text_content)
|
|
303
|
+
# URL-based images should remain unchanged
|
|
304
|
+
self.assertEqual(result[0]["content"][1]["source"]["url"], regular_url)
|
|
305
|
+
|
|
306
|
+
def test_sanitize_handles_non_dict_content(self):
|
|
307
|
+
input_data = [{"role": "user", "content": "Just text"}]
|
|
308
|
+
|
|
309
|
+
result = sanitize_openai(input_data)
|
|
310
|
+
self.assertEqual(result, input_data)
|
|
311
|
+
|
|
312
|
+
def test_sanitize_handles_none_input(self):
|
|
313
|
+
self.assertIsNone(sanitize_openai(None))
|
|
314
|
+
self.assertIsNone(sanitize_anthropic(None))
|
|
315
|
+
self.assertIsNone(sanitize_gemini(None))
|
|
316
|
+
self.assertIsNone(sanitize_langchain(None))
|
|
317
|
+
|
|
318
|
+
def test_sanitize_handles_single_message(self):
|
|
319
|
+
input_data = {
|
|
320
|
+
"role": "user",
|
|
321
|
+
"content": [
|
|
322
|
+
{
|
|
323
|
+
"type": "image_url",
|
|
324
|
+
"image_url": {"url": self.sample_base64_image},
|
|
325
|
+
}
|
|
326
|
+
],
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
result = sanitize_openai(input_data)
|
|
330
|
+
self.assertEqual(
|
|
331
|
+
result["content"][0]["image_url"]["url"], REDACTED_IMAGE_PLACEHOLDER
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class TestAIMultipartRequest(unittest.TestCase):
|
|
336
|
+
"""Test that _INTERNAL_LLMA_MULTIMODAL environment variable controls sanitization."""
|
|
337
|
+
|
|
338
|
+
def tearDown(self):
|
|
339
|
+
# Clean up environment variable after each test
|
|
340
|
+
if "_INTERNAL_LLMA_MULTIMODAL" in os.environ:
|
|
341
|
+
del os.environ["_INTERNAL_LLMA_MULTIMODAL"]
|
|
342
|
+
|
|
343
|
+
def test_multimodal_disabled_redacts_images(self):
|
|
344
|
+
"""When _INTERNAL_LLMA_MULTIMODAL is not set, images should be redacted."""
|
|
345
|
+
if "_INTERNAL_LLMA_MULTIMODAL" in os.environ:
|
|
346
|
+
del os.environ["_INTERNAL_LLMA_MULTIMODAL"]
|
|
347
|
+
|
|
348
|
+
base64_image = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..."
|
|
349
|
+
result = redact_base64_data_url(base64_image)
|
|
350
|
+
self.assertEqual(result, REDACTED_IMAGE_PLACEHOLDER)
|
|
351
|
+
|
|
352
|
+
def test_multimodal_enabled_preserves_images(self):
|
|
353
|
+
"""When _INTERNAL_LLMA_MULTIMODAL is true, images should be preserved."""
|
|
354
|
+
os.environ["_INTERNAL_LLMA_MULTIMODAL"] = "true"
|
|
355
|
+
|
|
356
|
+
base64_image = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..."
|
|
357
|
+
result = redact_base64_data_url(base64_image)
|
|
358
|
+
self.assertEqual(result, base64_image)
|
|
359
|
+
|
|
360
|
+
def test_multimodal_enabled_with_1(self):
|
|
361
|
+
"""_INTERNAL_LLMA_MULTIMODAL=1 should enable multimodal."""
|
|
362
|
+
os.environ["_INTERNAL_LLMA_MULTIMODAL"] = "1"
|
|
363
|
+
|
|
364
|
+
base64_image = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..."
|
|
365
|
+
result = redact_base64_data_url(base64_image)
|
|
366
|
+
self.assertEqual(result, base64_image)
|
|
367
|
+
|
|
368
|
+
def test_multimodal_enabled_with_yes(self):
|
|
369
|
+
"""_INTERNAL_LLMA_MULTIMODAL=yes should enable multimodal."""
|
|
370
|
+
os.environ["_INTERNAL_LLMA_MULTIMODAL"] = "yes"
|
|
371
|
+
|
|
372
|
+
base64_image = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..."
|
|
373
|
+
result = redact_base64_data_url(base64_image)
|
|
374
|
+
self.assertEqual(result, base64_image)
|
|
375
|
+
|
|
376
|
+
def test_multimodal_false_redacts_images(self):
|
|
377
|
+
"""_INTERNAL_LLMA_MULTIMODAL=false should still redact."""
|
|
378
|
+
os.environ["_INTERNAL_LLMA_MULTIMODAL"] = "false"
|
|
379
|
+
|
|
380
|
+
base64_image = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..."
|
|
381
|
+
result = redact_base64_data_url(base64_image)
|
|
382
|
+
self.assertEqual(result, REDACTED_IMAGE_PLACEHOLDER)
|
|
383
|
+
|
|
384
|
+
def test_anthropic_multimodal_enabled(self):
|
|
385
|
+
"""Anthropic images should be preserved when _INTERNAL_LLMA_MULTIMODAL is enabled."""
|
|
386
|
+
os.environ["_INTERNAL_LLMA_MULTIMODAL"] = "true"
|
|
387
|
+
|
|
388
|
+
input_data = [
|
|
389
|
+
{
|
|
390
|
+
"role": "user",
|
|
391
|
+
"content": [
|
|
392
|
+
{
|
|
393
|
+
"type": "image",
|
|
394
|
+
"source": {
|
|
395
|
+
"type": "base64",
|
|
396
|
+
"media_type": "image/jpeg",
|
|
397
|
+
"data": "base64data",
|
|
398
|
+
},
|
|
399
|
+
}
|
|
400
|
+
],
|
|
401
|
+
}
|
|
402
|
+
]
|
|
403
|
+
|
|
404
|
+
result = sanitize_anthropic(input_data)
|
|
405
|
+
self.assertEqual(result[0]["content"][0]["source"]["data"], "base64data")
|
|
406
|
+
|
|
407
|
+
def test_gemini_multimodal_enabled(self):
|
|
408
|
+
"""Gemini images should be preserved when _INTERNAL_LLMA_MULTIMODAL is enabled."""
|
|
409
|
+
os.environ["_INTERNAL_LLMA_MULTIMODAL"] = "true"
|
|
410
|
+
|
|
411
|
+
input_data = [
|
|
412
|
+
{
|
|
413
|
+
"parts": [
|
|
414
|
+
{"inline_data": {"mime_type": "image/jpeg", "data": "base64data"}}
|
|
415
|
+
]
|
|
416
|
+
}
|
|
417
|
+
]
|
|
418
|
+
|
|
419
|
+
result = sanitize_gemini(input_data)
|
|
420
|
+
self.assertEqual(result[0]["parts"][0]["inline_data"]["data"], "base64data")
|
|
421
|
+
|
|
422
|
+
def test_langchain_anthropic_style_multimodal_enabled(self):
|
|
423
|
+
"""LangChain Anthropic-style images should be preserved when _INTERNAL_LLMA_MULTIMODAL is enabled."""
|
|
424
|
+
os.environ["_INTERNAL_LLMA_MULTIMODAL"] = "true"
|
|
425
|
+
|
|
426
|
+
input_data = [
|
|
427
|
+
{
|
|
428
|
+
"role": "user",
|
|
429
|
+
"content": [
|
|
430
|
+
{
|
|
431
|
+
"type": "image",
|
|
432
|
+
"source": {"data": "base64data"},
|
|
433
|
+
}
|
|
434
|
+
],
|
|
435
|
+
}
|
|
436
|
+
]
|
|
437
|
+
|
|
438
|
+
result = sanitize_langchain(input_data)
|
|
439
|
+
self.assertEqual(result[0]["content"][0]["source"]["data"], "base64data")
|
|
440
|
+
|
|
441
|
+
def test_openai_audio_redacted_by_default(self):
|
|
442
|
+
"""OpenAI audio should be redacted when _INTERNAL_LLMA_MULTIMODAL is not set."""
|
|
443
|
+
if "_INTERNAL_LLMA_MULTIMODAL" in os.environ:
|
|
444
|
+
del os.environ["_INTERNAL_LLMA_MULTIMODAL"]
|
|
445
|
+
|
|
446
|
+
input_data = [
|
|
447
|
+
{
|
|
448
|
+
"role": "assistant",
|
|
449
|
+
"content": [
|
|
450
|
+
{"type": "audio", "data": "base64audiodata", "id": "audio_123"}
|
|
451
|
+
],
|
|
452
|
+
}
|
|
453
|
+
]
|
|
454
|
+
|
|
455
|
+
result = sanitize_openai(input_data)
|
|
456
|
+
self.assertEqual(result[0]["content"][0]["data"], REDACTED_IMAGE_PLACEHOLDER)
|
|
457
|
+
self.assertEqual(result[0]["content"][0]["id"], "audio_123")
|
|
458
|
+
|
|
459
|
+
def test_openai_audio_preserved_with_flag(self):
|
|
460
|
+
"""OpenAI audio should be preserved when _INTERNAL_LLMA_MULTIMODAL is enabled."""
|
|
461
|
+
os.environ["_INTERNAL_LLMA_MULTIMODAL"] = "true"
|
|
462
|
+
|
|
463
|
+
input_data = [
|
|
464
|
+
{
|
|
465
|
+
"role": "assistant",
|
|
466
|
+
"content": [
|
|
467
|
+
{"type": "audio", "data": "base64audiodata", "id": "audio_123"}
|
|
468
|
+
],
|
|
469
|
+
}
|
|
470
|
+
]
|
|
471
|
+
|
|
472
|
+
result = sanitize_openai(input_data)
|
|
473
|
+
self.assertEqual(result[0]["content"][0]["data"], "base64audiodata")
|
|
474
|
+
|
|
475
|
+
def test_gemini_audio_redacted_by_default(self):
|
|
476
|
+
"""Gemini audio should be redacted when _INTERNAL_LLMA_MULTIMODAL is not set."""
|
|
477
|
+
if "_INTERNAL_LLMA_MULTIMODAL" in os.environ:
|
|
478
|
+
del os.environ["_INTERNAL_LLMA_MULTIMODAL"]
|
|
479
|
+
|
|
480
|
+
input_data = [
|
|
481
|
+
{
|
|
482
|
+
"parts": [
|
|
483
|
+
{
|
|
484
|
+
"inline_data": {
|
|
485
|
+
"mime_type": "audio/L16;codec=pcm;rate=24000",
|
|
486
|
+
"data": "base64audiodata",
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
]
|
|
490
|
+
}
|
|
491
|
+
]
|
|
492
|
+
|
|
493
|
+
result = sanitize_gemini(input_data)
|
|
494
|
+
self.assertEqual(
|
|
495
|
+
result[0]["parts"][0]["inline_data"]["data"], REDACTED_IMAGE_PLACEHOLDER
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
def test_gemini_audio_preserved_with_flag(self):
|
|
499
|
+
"""Gemini audio should be preserved when _INTERNAL_LLMA_MULTIMODAL is enabled."""
|
|
500
|
+
os.environ["_INTERNAL_LLMA_MULTIMODAL"] = "true"
|
|
501
|
+
|
|
502
|
+
input_data = [
|
|
503
|
+
{
|
|
504
|
+
"parts": [
|
|
505
|
+
{
|
|
506
|
+
"inline_data": {
|
|
507
|
+
"mime_type": "audio/L16;codec=pcm;rate=24000",
|
|
508
|
+
"data": "base64audiodata",
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
]
|
|
512
|
+
}
|
|
513
|
+
]
|
|
514
|
+
|
|
515
|
+
result = sanitize_gemini(input_data)
|
|
516
|
+
self.assertEqual(
|
|
517
|
+
result[0]["parts"][0]["inline_data"]["data"], "base64audiodata"
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
if __name__ == "__main__":
|
|
522
|
+
unittest.main()
|