posthoganalytics 6.7.5__py3-none-any.whl → 7.4.3__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.
- posthoganalytics/__init__.py +84 -7
- posthoganalytics/ai/anthropic/anthropic_async.py +30 -67
- posthoganalytics/ai/anthropic/anthropic_converter.py +40 -0
- posthoganalytics/ai/gemini/__init__.py +3 -0
- posthoganalytics/ai/gemini/gemini.py +1 -1
- posthoganalytics/ai/gemini/gemini_async.py +423 -0
- posthoganalytics/ai/gemini/gemini_converter.py +160 -24
- posthoganalytics/ai/langchain/callbacks.py +55 -11
- posthoganalytics/ai/openai/openai.py +27 -2
- posthoganalytics/ai/openai/openai_async.py +49 -5
- posthoganalytics/ai/openai/openai_converter.py +130 -0
- posthoganalytics/ai/sanitization.py +27 -5
- posthoganalytics/ai/types.py +1 -0
- posthoganalytics/ai/utils.py +32 -2
- posthoganalytics/client.py +338 -90
- posthoganalytics/contexts.py +81 -0
- posthoganalytics/exception_utils.py +250 -2
- posthoganalytics/feature_flags.py +26 -10
- posthoganalytics/flag_definition_cache.py +127 -0
- posthoganalytics/integrations/django.py +149 -50
- posthoganalytics/request.py +203 -23
- posthoganalytics/test/test_client.py +250 -22
- posthoganalytics/test/test_exception_capture.py +418 -0
- posthoganalytics/test/test_feature_flag_result.py +441 -2
- posthoganalytics/test/test_feature_flags.py +306 -102
- posthoganalytics/test/test_flag_definition_cache.py +612 -0
- posthoganalytics/test/test_module.py +0 -8
- posthoganalytics/test/test_request.py +536 -0
- posthoganalytics/test/test_utils.py +4 -1
- posthoganalytics/types.py +40 -0
- posthoganalytics/version.py +1 -1
- {posthoganalytics-6.7.5.dist-info → posthoganalytics-7.4.3.dist-info}/METADATA +12 -12
- posthoganalytics-7.4.3.dist-info/RECORD +57 -0
- posthoganalytics-6.7.5.dist-info/RECORD +0 -54
- {posthoganalytics-6.7.5.dist-info → posthoganalytics-7.4.3.dist-info}/WHEEL +0 -0
- {posthoganalytics-6.7.5.dist-info → posthoganalytics-7.4.3.dist-info}/licenses/LICENSE +0 -0
- {posthoganalytics-6.7.5.dist-info → posthoganalytics-7.4.3.dist-info}/top_level.txt +0 -0
posthoganalytics/__init__.py
CHANGED
|
@@ -1,17 +1,61 @@
|
|
|
1
1
|
import datetime # noqa: F401
|
|
2
|
-
from typing import Callable, Dict, Optional
|
|
2
|
+
from typing import Any, Callable, Dict, Optional # noqa: F401
|
|
3
|
+
|
|
3
4
|
from typing_extensions import Unpack
|
|
4
5
|
|
|
5
|
-
from posthoganalytics.args import OptionalCaptureArgs, OptionalSetArgs
|
|
6
|
+
from posthoganalytics.args import ExceptionArg, OptionalCaptureArgs, OptionalSetArgs
|
|
6
7
|
from posthoganalytics.client import Client
|
|
8
|
+
from posthoganalytics.contexts import (
|
|
9
|
+
identify_context as inner_identify_context,
|
|
10
|
+
)
|
|
7
11
|
from posthoganalytics.contexts import (
|
|
8
12
|
new_context as inner_new_context,
|
|
13
|
+
)
|
|
14
|
+
from posthoganalytics.contexts import (
|
|
9
15
|
scoped as inner_scoped,
|
|
10
|
-
|
|
16
|
+
)
|
|
17
|
+
from posthoganalytics.contexts import (
|
|
18
|
+
set_capture_exception_code_variables_context as inner_set_capture_exception_code_variables_context,
|
|
19
|
+
)
|
|
20
|
+
from posthoganalytics.contexts import (
|
|
21
|
+
set_code_variables_ignore_patterns_context as inner_set_code_variables_ignore_patterns_context,
|
|
22
|
+
)
|
|
23
|
+
from posthoganalytics.contexts import (
|
|
24
|
+
set_code_variables_mask_patterns_context as inner_set_code_variables_mask_patterns_context,
|
|
25
|
+
)
|
|
26
|
+
from posthoganalytics.contexts import (
|
|
11
27
|
set_context_session as inner_set_context_session,
|
|
12
|
-
identify_context as inner_identify_context,
|
|
13
28
|
)
|
|
14
|
-
from posthoganalytics.
|
|
29
|
+
from posthoganalytics.contexts import (
|
|
30
|
+
tag as inner_tag,
|
|
31
|
+
)
|
|
32
|
+
from posthoganalytics.exception_utils import (
|
|
33
|
+
DEFAULT_CODE_VARIABLES_IGNORE_PATTERNS,
|
|
34
|
+
DEFAULT_CODE_VARIABLES_MASK_PATTERNS,
|
|
35
|
+
)
|
|
36
|
+
from posthoganalytics.feature_flags import (
|
|
37
|
+
InconclusiveMatchError as InconclusiveMatchError,
|
|
38
|
+
)
|
|
39
|
+
from posthoganalytics.feature_flags import (
|
|
40
|
+
RequiresServerEvaluation as RequiresServerEvaluation,
|
|
41
|
+
)
|
|
42
|
+
from posthoganalytics.flag_definition_cache import (
|
|
43
|
+
FlagDefinitionCacheData as FlagDefinitionCacheData,
|
|
44
|
+
FlagDefinitionCacheProvider as FlagDefinitionCacheProvider,
|
|
45
|
+
)
|
|
46
|
+
from posthoganalytics.request import (
|
|
47
|
+
disable_connection_reuse as disable_connection_reuse,
|
|
48
|
+
enable_keep_alive as enable_keep_alive,
|
|
49
|
+
set_socket_options as set_socket_options,
|
|
50
|
+
SocketOptions as SocketOptions,
|
|
51
|
+
)
|
|
52
|
+
from posthoganalytics.types import (
|
|
53
|
+
FeatureFlag,
|
|
54
|
+
FlagsAndPayloads,
|
|
55
|
+
)
|
|
56
|
+
from posthoganalytics.types import (
|
|
57
|
+
FeatureFlagResult as FeatureFlagResult,
|
|
58
|
+
)
|
|
15
59
|
from posthoganalytics.version import VERSION
|
|
16
60
|
|
|
17
61
|
__version__ = VERSION
|
|
@@ -19,13 +63,14 @@ __version__ = VERSION
|
|
|
19
63
|
"""Context management."""
|
|
20
64
|
|
|
21
65
|
|
|
22
|
-
def new_context(fresh=False, capture_exceptions=True):
|
|
66
|
+
def new_context(fresh=False, capture_exceptions=True, client=None):
|
|
23
67
|
"""
|
|
24
68
|
Create a new context scope that will be active for the duration of the with block.
|
|
25
69
|
|
|
26
70
|
Args:
|
|
27
71
|
fresh: Whether to start with a fresh context (default: False)
|
|
28
72
|
capture_exceptions: Whether to capture exceptions raised within the context (default: True)
|
|
73
|
+
client: Optional Posthog client instance to use for this context (default: None)
|
|
29
74
|
|
|
30
75
|
Examples:
|
|
31
76
|
```python
|
|
@@ -38,7 +83,9 @@ def new_context(fresh=False, capture_exceptions=True):
|
|
|
38
83
|
Category:
|
|
39
84
|
Contexts
|
|
40
85
|
"""
|
|
41
|
-
return inner_new_context(
|
|
86
|
+
return inner_new_context(
|
|
87
|
+
fresh=fresh, capture_exceptions=capture_exceptions, client=client
|
|
88
|
+
)
|
|
42
89
|
|
|
43
90
|
|
|
44
91
|
def scoped(fresh=False, capture_exceptions=True):
|
|
@@ -102,6 +149,27 @@ def identify_context(distinct_id: str):
|
|
|
102
149
|
return inner_identify_context(distinct_id)
|
|
103
150
|
|
|
104
151
|
|
|
152
|
+
def set_capture_exception_code_variables_context(enabled: bool):
|
|
153
|
+
"""
|
|
154
|
+
Set whether code variables are captured for the current context.
|
|
155
|
+
"""
|
|
156
|
+
return inner_set_capture_exception_code_variables_context(enabled)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def set_code_variables_mask_patterns_context(mask_patterns: list):
|
|
160
|
+
"""
|
|
161
|
+
Variable names matching these patterns will be masked with *** when capturing code variables.
|
|
162
|
+
"""
|
|
163
|
+
return inner_set_code_variables_mask_patterns_context(mask_patterns)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def set_code_variables_ignore_patterns_context(ignore_patterns: list):
|
|
167
|
+
"""
|
|
168
|
+
Variable names matching these patterns will be ignored completely when capturing code variables.
|
|
169
|
+
"""
|
|
170
|
+
return inner_set_code_variables_ignore_patterns_context(ignore_patterns)
|
|
171
|
+
|
|
172
|
+
|
|
105
173
|
def tag(name: str, value: Any):
|
|
106
174
|
"""
|
|
107
175
|
Add a tag to the current context.
|
|
@@ -149,6 +217,11 @@ enable_local_evaluation = True # type: bool
|
|
|
149
217
|
|
|
150
218
|
default_client = None # type: Optional[Client]
|
|
151
219
|
|
|
220
|
+
capture_exception_code_variables = False
|
|
221
|
+
code_variables_mask_patterns = DEFAULT_CODE_VARIABLES_MASK_PATTERNS
|
|
222
|
+
code_variables_ignore_patterns = DEFAULT_CODE_VARIABLES_IGNORE_PATTERNS
|
|
223
|
+
in_app_modules = None # type: Optional[list[str]]
|
|
224
|
+
|
|
152
225
|
|
|
153
226
|
# NOTE - this and following functions take unpacked kwargs because we needed to make
|
|
154
227
|
# it impossible to write `posthog.capture(distinct-id, event-name)` - basically, to enforce
|
|
@@ -743,6 +816,10 @@ def setup() -> Client:
|
|
|
743
816
|
enable_exception_autocapture=enable_exception_autocapture,
|
|
744
817
|
log_captured_exceptions=log_captured_exceptions,
|
|
745
818
|
enable_local_evaluation=enable_local_evaluation,
|
|
819
|
+
capture_exception_code_variables=capture_exception_code_variables,
|
|
820
|
+
code_variables_mask_patterns=code_variables_mask_patterns,
|
|
821
|
+
code_variables_ignore_patterns=code_variables_ignore_patterns,
|
|
822
|
+
in_app_modules=in_app_modules,
|
|
746
823
|
)
|
|
747
824
|
|
|
748
825
|
# always set incase user changes it
|
|
@@ -14,14 +14,9 @@ from posthoganalytics import setup
|
|
|
14
14
|
from posthoganalytics.ai.types import StreamingContentBlock, TokenUsage, ToolInProgress
|
|
15
15
|
from posthoganalytics.ai.utils import (
|
|
16
16
|
call_llm_and_track_usage_async,
|
|
17
|
-
extract_available_tool_calls,
|
|
18
|
-
get_model_params,
|
|
19
|
-
merge_system_prompt,
|
|
20
17
|
merge_usage_stats,
|
|
21
|
-
with_privacy_mode,
|
|
22
18
|
)
|
|
23
19
|
from posthoganalytics.ai.anthropic.anthropic_converter import (
|
|
24
|
-
format_anthropic_streaming_content,
|
|
25
20
|
extract_anthropic_usage_from_event,
|
|
26
21
|
handle_anthropic_content_block_start,
|
|
27
22
|
handle_anthropic_text_delta,
|
|
@@ -220,66 +215,34 @@ class AsyncWrappedMessages(AsyncMessages):
|
|
|
220
215
|
content_blocks: List[StreamingContentBlock],
|
|
221
216
|
accumulated_content: str,
|
|
222
217
|
):
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
"$ai_provider": "anthropic",
|
|
243
|
-
"$ai_model": kwargs.get("model"),
|
|
244
|
-
"$ai_model_parameters": get_model_params(kwargs),
|
|
245
|
-
"$ai_input": with_privacy_mode(
|
|
246
|
-
self._client._ph_client,
|
|
247
|
-
posthog_privacy_mode,
|
|
248
|
-
sanitize_anthropic(merge_system_prompt(kwargs, "anthropic")),
|
|
249
|
-
),
|
|
250
|
-
"$ai_output_choices": with_privacy_mode(
|
|
251
|
-
self._client._ph_client,
|
|
252
|
-
posthog_privacy_mode,
|
|
253
|
-
formatted_output,
|
|
254
|
-
),
|
|
255
|
-
"$ai_http_status": 200,
|
|
256
|
-
"$ai_input_tokens": usage_stats.get("input_tokens", 0),
|
|
257
|
-
"$ai_output_tokens": usage_stats.get("output_tokens", 0),
|
|
258
|
-
"$ai_cache_read_input_tokens": usage_stats.get(
|
|
259
|
-
"cache_read_input_tokens", 0
|
|
260
|
-
),
|
|
261
|
-
"$ai_cache_creation_input_tokens": usage_stats.get(
|
|
262
|
-
"cache_creation_input_tokens", 0
|
|
218
|
+
from posthoganalytics.ai.types import StreamingEventData
|
|
219
|
+
from posthoganalytics.ai.anthropic.anthropic_converter import (
|
|
220
|
+
format_anthropic_streaming_input,
|
|
221
|
+
format_anthropic_streaming_output_complete,
|
|
222
|
+
)
|
|
223
|
+
from posthoganalytics.ai.utils import capture_streaming_event
|
|
224
|
+
|
|
225
|
+
# Prepare standardized event data
|
|
226
|
+
formatted_input = format_anthropic_streaming_input(kwargs)
|
|
227
|
+
sanitized_input = sanitize_anthropic(formatted_input)
|
|
228
|
+
|
|
229
|
+
event_data = StreamingEventData(
|
|
230
|
+
provider="anthropic",
|
|
231
|
+
model=kwargs.get("model", "unknown"),
|
|
232
|
+
base_url=str(self._client.base_url),
|
|
233
|
+
kwargs=kwargs,
|
|
234
|
+
formatted_input=sanitized_input,
|
|
235
|
+
formatted_output=format_anthropic_streaming_output_complete(
|
|
236
|
+
content_blocks, accumulated_content
|
|
263
237
|
),
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if posthog_distinct_id is None:
|
|
277
|
-
event_properties["$process_person_profile"] = False
|
|
278
|
-
|
|
279
|
-
if hasattr(self._client._ph_client, "capture"):
|
|
280
|
-
self._client._ph_client.capture(
|
|
281
|
-
distinct_id=posthog_distinct_id or posthog_trace_id,
|
|
282
|
-
event="$ai_generation",
|
|
283
|
-
properties=event_properties,
|
|
284
|
-
groups=posthog_groups,
|
|
285
|
-
)
|
|
238
|
+
usage_stats=usage_stats,
|
|
239
|
+
latency=latency,
|
|
240
|
+
distinct_id=posthog_distinct_id,
|
|
241
|
+
trace_id=posthog_trace_id,
|
|
242
|
+
properties=posthog_properties,
|
|
243
|
+
privacy_mode=posthog_privacy_mode,
|
|
244
|
+
groups=posthog_groups,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# Use the common capture function
|
|
248
|
+
capture_streaming_event(self._client._ph_client, event_data)
|
|
@@ -163,6 +163,32 @@ def format_anthropic_streaming_content(
|
|
|
163
163
|
return formatted
|
|
164
164
|
|
|
165
165
|
|
|
166
|
+
def extract_anthropic_web_search_count(response: Any) -> int:
|
|
167
|
+
"""
|
|
168
|
+
Extract web search count from Anthropic response.
|
|
169
|
+
|
|
170
|
+
Anthropic provides exact web search counts via usage.server_tool_use.web_search_requests.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
response: The response from Anthropic API
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Number of web search requests (0 if none)
|
|
177
|
+
"""
|
|
178
|
+
if not hasattr(response, "usage"):
|
|
179
|
+
return 0
|
|
180
|
+
|
|
181
|
+
if not hasattr(response.usage, "server_tool_use"):
|
|
182
|
+
return 0
|
|
183
|
+
|
|
184
|
+
server_tool_use = response.usage.server_tool_use
|
|
185
|
+
|
|
186
|
+
if hasattr(server_tool_use, "web_search_requests"):
|
|
187
|
+
return max(0, int(getattr(server_tool_use, "web_search_requests", 0)))
|
|
188
|
+
|
|
189
|
+
return 0
|
|
190
|
+
|
|
191
|
+
|
|
166
192
|
def extract_anthropic_usage_from_response(response: Any) -> TokenUsage:
|
|
167
193
|
"""
|
|
168
194
|
Extract usage from a full Anthropic response (non-streaming).
|
|
@@ -191,6 +217,10 @@ def extract_anthropic_usage_from_response(response: Any) -> TokenUsage:
|
|
|
191
217
|
if cache_creation and cache_creation > 0:
|
|
192
218
|
result["cache_creation_input_tokens"] = cache_creation
|
|
193
219
|
|
|
220
|
+
web_search_count = extract_anthropic_web_search_count(response)
|
|
221
|
+
if web_search_count > 0:
|
|
222
|
+
result["web_search_count"] = web_search_count
|
|
223
|
+
|
|
194
224
|
return result
|
|
195
225
|
|
|
196
226
|
|
|
@@ -222,6 +252,16 @@ def extract_anthropic_usage_from_event(event: Any) -> TokenUsage:
|
|
|
222
252
|
if hasattr(event, "usage") and event.usage:
|
|
223
253
|
usage["output_tokens"] = getattr(event.usage, "output_tokens", 0)
|
|
224
254
|
|
|
255
|
+
# Extract web search count from usage
|
|
256
|
+
if hasattr(event.usage, "server_tool_use"):
|
|
257
|
+
server_tool_use = event.usage.server_tool_use
|
|
258
|
+
if hasattr(server_tool_use, "web_search_requests"):
|
|
259
|
+
web_search_count = int(
|
|
260
|
+
getattr(server_tool_use, "web_search_requests", 0)
|
|
261
|
+
)
|
|
262
|
+
if web_search_count > 0:
|
|
263
|
+
usage["web_search_count"] = web_search_count
|
|
264
|
+
|
|
225
265
|
return usage
|
|
226
266
|
|
|
227
267
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from .gemini import Client
|
|
2
|
+
from .gemini_async import AsyncClient
|
|
2
3
|
from .gemini_converter import (
|
|
3
4
|
format_gemini_input,
|
|
4
5
|
format_gemini_response,
|
|
@@ -9,12 +10,14 @@ from .gemini_converter import (
|
|
|
9
10
|
# Create a genai-like module for perfect drop-in replacement
|
|
10
11
|
class _GenAI:
|
|
11
12
|
Client = Client
|
|
13
|
+
AsyncClient = AsyncClient
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
genai = _GenAI()
|
|
15
17
|
|
|
16
18
|
__all__ = [
|
|
17
19
|
"Client",
|
|
20
|
+
"AsyncClient",
|
|
18
21
|
"genai",
|
|
19
22
|
"format_gemini_input",
|
|
20
23
|
"format_gemini_response",
|