Skip to content

Commit 9790711

Browse files
committed
feat: AsyncPosthog privacy_mode and enable_full_ai_capture
1 parent 1da0b99 commit 9790711

3 files changed

Lines changed: 42 additions & 6 deletions

File tree

‎posthog/async_client.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ class AsyncClient:
113113
events, on a separate queue that posts to the AI capture endpoint.
114114
``flush()``, ``join()``, and ``shutdown()`` are awaitable lifecycle methods.
115115
116-
The ``capture_ai_*`` options configure the AI queue the same way as on
117-
:class:`posthog.Client`.
116+
The ``capture_ai_*``, ``privacy_mode`` and ``enable_full_ai_capture``
117+
options work the same way as on :class:`posthog.Client`.
118118
"""
119119

120120
log = logging.getLogger("posthog")
@@ -153,6 +153,8 @@ def __init__(
153153
capture_ai_max_queue_size: int = 1000,
154154
capture_ai_timeout: float = 30,
155155
capture_ai_max_event_bytes: int = AI_MAX_MSG_SIZE,
156+
privacy_mode: bool = False,
157+
enable_full_ai_capture: bool = False,
156158
capture_trace_context: bool = False,
157159
secret_key: Optional[str] = None,
158160
personal_api_key: Optional[str] = None,
@@ -196,6 +198,8 @@ def __init__(
196198
capture_ai_compression
197199
)
198200
self.capture_trace_context = capture_trace_context
201+
self.privacy_mode = privacy_mode
202+
self.enable_full_ai_capture = enable_full_ai_capture is True
199203
if personal_api_key is not None and secret_key is None:
200204
warnings.warn(
201205
"`personal_api_key` is deprecated; use `secret_key` instead.",

‎posthog/test/test_ai_capture_lane.py‎

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import posthog
1010

1111
from posthog.ai.utils import _capture_ai_event, finalize_ai_content, with_privacy_mode
12+
from posthog.async_client import AsyncPosthog
1213
from posthog.capture_compression import CAPTURE_COMPRESSION_ENV_VAR, CaptureCompression
1314
from posthog.client import Client
1415
from posthog.consumer import AI_MAX_MSG_SIZE, AI_MAX_PROPERTIES_SIZE, MAX_MSG_SIZE
@@ -697,14 +698,16 @@ def drop_uuid(event):
697698
class TestCaptureAiPrivacyMode(unittest.TestCase):
698699
"""Privacy mode always wins over `enable_full_ai_capture`."""
699700

700-
def test_privacy_mode_strips_content_despite_full_ai_capture(self):
701-
client = Client(
701+
@parameterized.expand([("sync", Client), ("async", AsyncPosthog)])
702+
def test_privacy_mode_strips_content_despite_full_ai_capture(
703+
self, _name, client_cls
704+
):
705+
client = client_cls(
702706
TEST_API_KEY,
703707
send=False,
704708
enable_full_ai_capture=True,
705709
privacy_mode=True,
706710
)
707-
self.addCleanup(client.join)
708711
payload = {"role": "user", "content": "sensitive prompt"}
709712

710713
sanitized = with_privacy_mode(
@@ -713,6 +716,33 @@ def test_privacy_mode_strips_content_despite_full_ai_capture(self):
713716

714717
self.assertIsNone(sanitized)
715718

719+
@parameterized.expand(
720+
[
721+
("sync_default", Client, False),
722+
("sync_full_capture", Client, True),
723+
("async_default", AsyncPosthog, False),
724+
("async_full_capture", AsyncPosthog, True),
725+
]
726+
)
727+
def test_full_ai_capture_controls_media_redaction(
728+
self, _name, client_cls, full_capture
729+
):
730+
client = client_cls(
731+
TEST_API_KEY, send=False, enable_full_ai_capture=full_capture
732+
)
733+
image = "data:image/jpeg;base64," + "A" * 64
734+
payload = [
735+
{
736+
"role": "user",
737+
"content": [{"type": "image_url", "image_url": {"url": image}}],
738+
}
739+
]
740+
741+
sanitized = finalize_ai_content(payload, ph_client=client)
742+
743+
sent_url = sanitized[0]["content"][0]["image_url"]["url"]
744+
self.assertEqual(sent_url == image, full_capture)
745+
716746

717747
if __name__ == "__main__":
718748
unittest.main()

‎references/public_api_snapshot.txt‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,7 @@ attribute posthog.async_client.AsyncClient.debug = debug
668668
attribute posthog.async_client.AsyncClient.disable_geoip = disable_geoip
669669
attribute posthog.async_client.AsyncClient.disabled = disabled or not self.api_key
670670
attribute posthog.async_client.AsyncClient.distinct_ids_feature_flags_reported = SizeLimitedDict(_MAX_DICT_SIZE, set)
671+
attribute posthog.async_client.AsyncClient.enable_full_ai_capture = enable_full_ai_capture is True
671672
attribute posthog.async_client.AsyncClient.feature_flags_request_max_retries = max(0, feature_flags_request_max_retries)
672673
attribute posthog.async_client.AsyncClient.feature_flags_request_timeout_seconds = feature_flags_request_timeout_seconds
673674
attribute posthog.async_client.AsyncClient.historical_migration = historical_migration
@@ -679,6 +680,7 @@ attribute posthog.async_client.AsyncClient.log_captured_exceptions = log_capture
679680
attribute posthog.async_client.AsyncClient.max_retries = max(0, max_retries)
680681
attribute posthog.async_client.AsyncClient.on_error = on_error
681682
attribute posthog.async_client.AsyncClient.personal_api_key = self.secret_key
683+
attribute posthog.async_client.AsyncClient.privacy_mode = privacy_mode
682684
attribute posthog.async_client.AsyncClient.project_root = os.getcwd()
683685
attribute posthog.async_client.AsyncClient.raw_host = normalize_host(host)
684686
attribute posthog.async_client.AsyncClient.secret_key = (resolved_secret_key.strip() if isinstance(resolved_secret_key, str) else resolved_secret_key) or None
@@ -1177,7 +1179,7 @@ class posthog.ai.types.TokenUsage
11771179
class posthog.ai.types.ToolInProgress
11781180
class posthog.args.OptionalCaptureArgs
11791181
class posthog.args.OptionalSetArgs
1180-
class posthog.async_client.AsyncClient(project_api_key: str, host: Optional[str] = None, *, debug: bool = False, max_queue_size: int = 10000, send: bool = True, on_error=None, flush_at: int = 100, flush_interval: float = 5.0, max_retries: int = 3, timeout: int = 15, thread: int = 1, disabled: bool = False, disable_geoip: bool = True, is_server: bool = True, historical_migration: bool = False, super_properties: Optional[dict[str, Any]] = None, super_options: Optional[dict[str, Any]] = None, before_send=None, log_captured_exceptions: bool = False, project_root: Optional[str] = None, capture_exception_code_variables: bool = False, code_variables_mask_patterns=None, code_variables_ignore_patterns=None, code_variables_mask_url_credentials=None, code_variables_detect_secrets=None, in_app_modules: Optional[list[str]] = None, capture_compression: Optional[Union[CaptureCompression, str]] = None, capture_ai_compression: Optional[Union[CaptureCompression, str]] = None, capture_ai_max_queue_size: int = 1000, capture_ai_timeout: float = 30, capture_ai_max_event_bytes: int = AI_MAX_MSG_SIZE, capture_trace_context: bool = False, secret_key: Optional[str] = None, personal_api_key: Optional[str] = None, feature_flags_request_timeout_seconds: int = 3, feature_flags_request_max_retries: int = 1)
1182+
class posthog.async_client.AsyncClient(project_api_key: str, host: Optional[str] = None, *, debug: bool = False, max_queue_size: int = 10000, send: bool = True, on_error=None, flush_at: int = 100, flush_interval: float = 5.0, max_retries: int = 3, timeout: int = 15, thread: int = 1, disabled: bool = False, disable_geoip: bool = True, is_server: bool = True, historical_migration: bool = False, super_properties: Optional[dict[str, Any]] = None, super_options: Optional[dict[str, Any]] = None, before_send=None, log_captured_exceptions: bool = False, project_root: Optional[str] = None, capture_exception_code_variables: bool = False, code_variables_mask_patterns=None, code_variables_ignore_patterns=None, code_variables_mask_url_credentials=None, code_variables_detect_secrets=None, in_app_modules: Optional[list[str]] = None, capture_compression: Optional[Union[CaptureCompression, str]] = None, capture_ai_compression: Optional[Union[CaptureCompression, str]] = None, capture_ai_max_queue_size: int = 1000, capture_ai_timeout: float = 30, capture_ai_max_event_bytes: int = AI_MAX_MSG_SIZE, privacy_mode: bool = False, enable_full_ai_capture: bool = False, capture_trace_context: bool = False, secret_key: Optional[str] = None, personal_api_key: Optional[str] = None, feature_flags_request_timeout_seconds: int = 3, feature_flags_request_max_retries: int = 1)
11811183
class posthog.async_client.AsyncPosthog
11821184
class posthog.bucketed_rate_limiter.BucketedRateLimiter(bucket_size: Number, refill_rate: Number, refill_interval_seconds: Number, on_bucket_rate_limited: Optional[Callable[[Hashable], None]] = None, clock: Callable[[], float] = time.monotonic)
11831185
class posthog.capture_compression.CaptureCompression

0 commit comments

Comments
 (0)