posthoganalytics 6.7.0__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/__init__.py +10 -0
- posthoganalytics/ai/anthropic/anthropic.py +95 -65
- posthoganalytics/ai/anthropic/anthropic_async.py +95 -65
- posthoganalytics/ai/anthropic/anthropic_converter.py +443 -0
- posthoganalytics/ai/gemini/__init__.py +15 -1
- posthoganalytics/ai/gemini/gemini.py +66 -71
- posthoganalytics/ai/gemini/gemini_async.py +423 -0
- posthoganalytics/ai/gemini/gemini_converter.py +652 -0
- posthoganalytics/ai/langchain/callbacks.py +58 -13
- posthoganalytics/ai/openai/__init__.py +16 -1
- posthoganalytics/ai/openai/openai.py +140 -149
- posthoganalytics/ai/openai/openai_async.py +127 -82
- posthoganalytics/ai/openai/openai_converter.py +741 -0
- posthoganalytics/ai/sanitization.py +248 -0
- posthoganalytics/ai/types.py +125 -0
- posthoganalytics/ai/utils.py +339 -356
- posthoganalytics/client.py +345 -97
- 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 +157 -19
- 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 +308 -104
- 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.0.dist-info → posthoganalytics-7.4.3.dist-info}/METADATA +12 -12
- posthoganalytics-7.4.3.dist-info/RECORD +57 -0
- posthoganalytics-6.7.0.dist-info/RECORD +0 -49
- {posthoganalytics-6.7.0.dist-info → posthoganalytics-7.4.3.dist-info}/WHEEL +0 -0
- {posthoganalytics-6.7.0.dist-info → posthoganalytics-7.4.3.dist-info}/licenses/LICENSE +0 -0
- {posthoganalytics-6.7.0.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
|
|
@@ -6,6 +6,12 @@ from .anthropic_providers import (
|
|
|
6
6
|
AsyncAnthropicBedrock,
|
|
7
7
|
AsyncAnthropicVertex,
|
|
8
8
|
)
|
|
9
|
+
from .anthropic_converter import (
|
|
10
|
+
format_anthropic_response,
|
|
11
|
+
format_anthropic_input,
|
|
12
|
+
extract_anthropic_tools,
|
|
13
|
+
format_anthropic_streaming_content,
|
|
14
|
+
)
|
|
9
15
|
|
|
10
16
|
__all__ = [
|
|
11
17
|
"Anthropic",
|
|
@@ -14,4 +20,8 @@ __all__ = [
|
|
|
14
20
|
"AsyncAnthropicBedrock",
|
|
15
21
|
"AnthropicVertex",
|
|
16
22
|
"AsyncAnthropicVertex",
|
|
23
|
+
"format_anthropic_response",
|
|
24
|
+
"format_anthropic_input",
|
|
25
|
+
"extract_anthropic_tools",
|
|
26
|
+
"format_anthropic_streaming_content",
|
|
17
27
|
]
|
|
@@ -8,14 +8,21 @@ except ImportError:
|
|
|
8
8
|
|
|
9
9
|
import time
|
|
10
10
|
import uuid
|
|
11
|
-
from typing import Any, Dict, Optional
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
12
12
|
|
|
13
|
+
from posthoganalytics.ai.types import StreamingContentBlock, TokenUsage, ToolInProgress
|
|
13
14
|
from posthoganalytics.ai.utils import (
|
|
14
15
|
call_llm_and_track_usage,
|
|
15
|
-
|
|
16
|
-
merge_system_prompt,
|
|
17
|
-
with_privacy_mode,
|
|
16
|
+
merge_usage_stats,
|
|
18
17
|
)
|
|
18
|
+
from posthoganalytics.ai.anthropic.anthropic_converter import (
|
|
19
|
+
extract_anthropic_usage_from_event,
|
|
20
|
+
handle_anthropic_content_block_start,
|
|
21
|
+
handle_anthropic_text_delta,
|
|
22
|
+
handle_anthropic_tool_delta,
|
|
23
|
+
finalize_anthropic_tool_input,
|
|
24
|
+
)
|
|
25
|
+
from posthoganalytics.ai.sanitization import sanitize_anthropic
|
|
19
26
|
from posthoganalytics.client import Client as PostHogClient
|
|
20
27
|
from posthoganalytics import setup
|
|
21
28
|
|
|
@@ -61,6 +68,7 @@ class WrappedMessages(Messages):
|
|
|
61
68
|
posthog_groups: Optional group analytics properties
|
|
62
69
|
**kwargs: Arguments passed to Anthropic's messages.create
|
|
63
70
|
"""
|
|
71
|
+
|
|
64
72
|
if posthog_trace_id is None:
|
|
65
73
|
posthog_trace_id = str(uuid.uuid4())
|
|
66
74
|
|
|
@@ -118,35 +126,66 @@ class WrappedMessages(Messages):
|
|
|
118
126
|
**kwargs: Any,
|
|
119
127
|
):
|
|
120
128
|
start_time = time.time()
|
|
121
|
-
usage_stats:
|
|
122
|
-
accumulated_content =
|
|
129
|
+
usage_stats: TokenUsage = TokenUsage(input_tokens=0, output_tokens=0)
|
|
130
|
+
accumulated_content = ""
|
|
131
|
+
content_blocks: List[StreamingContentBlock] = []
|
|
132
|
+
tools_in_progress: Dict[str, ToolInProgress] = {}
|
|
133
|
+
current_text_block: Optional[StreamingContentBlock] = None
|
|
123
134
|
response = super().create(**kwargs)
|
|
124
135
|
|
|
125
136
|
def generator():
|
|
126
137
|
nonlocal usage_stats
|
|
127
|
-
nonlocal accumulated_content
|
|
138
|
+
nonlocal accumulated_content
|
|
139
|
+
nonlocal content_blocks
|
|
140
|
+
nonlocal tools_in_progress
|
|
141
|
+
nonlocal current_text_block
|
|
142
|
+
|
|
128
143
|
try:
|
|
129
144
|
for event in response:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
145
|
+
# Extract usage stats from event
|
|
146
|
+
event_usage = extract_anthropic_usage_from_event(event)
|
|
147
|
+
merge_usage_stats(usage_stats, event_usage)
|
|
148
|
+
|
|
149
|
+
# Handle content block start events
|
|
150
|
+
if hasattr(event, "type") and event.type == "content_block_start":
|
|
151
|
+
block, tool = handle_anthropic_content_block_start(event)
|
|
152
|
+
|
|
153
|
+
if block:
|
|
154
|
+
content_blocks.append(block)
|
|
155
|
+
|
|
156
|
+
if block.get("type") == "text":
|
|
157
|
+
current_text_block = block
|
|
158
|
+
else:
|
|
159
|
+
current_text_block = None
|
|
160
|
+
|
|
161
|
+
if tool:
|
|
162
|
+
tool_id = tool["block"].get("id")
|
|
163
|
+
if tool_id:
|
|
164
|
+
tools_in_progress[tool_id] = tool
|
|
165
|
+
|
|
166
|
+
# Handle text delta events
|
|
167
|
+
delta_text = handle_anthropic_text_delta(event, current_text_block)
|
|
168
|
+
|
|
169
|
+
if delta_text:
|
|
170
|
+
accumulated_content += delta_text
|
|
171
|
+
|
|
172
|
+
# Handle tool input delta events
|
|
173
|
+
handle_anthropic_tool_delta(
|
|
174
|
+
event, content_blocks, tools_in_progress
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Handle content block stop events
|
|
178
|
+
if hasattr(event, "type") and event.type == "content_block_stop":
|
|
179
|
+
current_text_block = None
|
|
180
|
+
finalize_anthropic_tool_input(
|
|
181
|
+
event, content_blocks, tools_in_progress
|
|
182
|
+
)
|
|
143
183
|
|
|
144
184
|
yield event
|
|
145
185
|
|
|
146
186
|
finally:
|
|
147
187
|
end_time = time.time()
|
|
148
188
|
latency = end_time - start_time
|
|
149
|
-
output = "".join(accumulated_content)
|
|
150
189
|
|
|
151
190
|
self._capture_streaming_event(
|
|
152
191
|
posthog_distinct_id,
|
|
@@ -157,7 +196,8 @@ class WrappedMessages(Messages):
|
|
|
157
196
|
kwargs,
|
|
158
197
|
usage_stats,
|
|
159
198
|
latency,
|
|
160
|
-
|
|
199
|
+
content_blocks,
|
|
200
|
+
accumulated_content,
|
|
161
201
|
)
|
|
162
202
|
|
|
163
203
|
return generator()
|
|
@@ -170,49 +210,39 @@ class WrappedMessages(Messages):
|
|
|
170
210
|
posthog_privacy_mode: bool,
|
|
171
211
|
posthog_groups: Optional[Dict[str, Any]],
|
|
172
212
|
kwargs: Dict[str, Any],
|
|
173
|
-
usage_stats:
|
|
213
|
+
usage_stats: TokenUsage,
|
|
174
214
|
latency: float,
|
|
175
|
-
|
|
215
|
+
content_blocks: List[StreamingContentBlock],
|
|
216
|
+
accumulated_content: str,
|
|
176
217
|
):
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
"
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
"$ai_output_tokens": usage_stats.get("output_tokens", 0),
|
|
197
|
-
"$ai_cache_read_input_tokens": usage_stats.get(
|
|
198
|
-
"cache_read_input_tokens", 0
|
|
199
|
-
),
|
|
200
|
-
"$ai_cache_creation_input_tokens": usage_stats.get(
|
|
201
|
-
"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
|
|
202
237
|
),
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
distinct_id=posthog_distinct_id or posthog_trace_id,
|
|
215
|
-
event="$ai_generation",
|
|
216
|
-
properties=event_properties,
|
|
217
|
-
groups=posthog_groups,
|
|
218
|
-
)
|
|
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)
|
|
@@ -8,15 +8,22 @@ except ImportError:
|
|
|
8
8
|
|
|
9
9
|
import time
|
|
10
10
|
import uuid
|
|
11
|
-
from typing import Any, Dict, Optional
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
12
12
|
|
|
13
13
|
from posthoganalytics import setup
|
|
14
|
+
from posthoganalytics.ai.types import StreamingContentBlock, TokenUsage, ToolInProgress
|
|
14
15
|
from posthoganalytics.ai.utils import (
|
|
15
16
|
call_llm_and_track_usage_async,
|
|
16
|
-
|
|
17
|
-
merge_system_prompt,
|
|
18
|
-
with_privacy_mode,
|
|
17
|
+
merge_usage_stats,
|
|
19
18
|
)
|
|
19
|
+
from posthoganalytics.ai.anthropic.anthropic_converter import (
|
|
20
|
+
extract_anthropic_usage_from_event,
|
|
21
|
+
handle_anthropic_content_block_start,
|
|
22
|
+
handle_anthropic_text_delta,
|
|
23
|
+
handle_anthropic_tool_delta,
|
|
24
|
+
finalize_anthropic_tool_input,
|
|
25
|
+
)
|
|
26
|
+
from posthoganalytics.ai.sanitization import sanitize_anthropic
|
|
20
27
|
from posthoganalytics.client import Client as PostHogClient
|
|
21
28
|
|
|
22
29
|
|
|
@@ -61,6 +68,7 @@ class AsyncWrappedMessages(AsyncMessages):
|
|
|
61
68
|
posthog_groups: Optional group analytics properties
|
|
62
69
|
**kwargs: Arguments passed to Anthropic's messages.create
|
|
63
70
|
"""
|
|
71
|
+
|
|
64
72
|
if posthog_trace_id is None:
|
|
65
73
|
posthog_trace_id = str(uuid.uuid4())
|
|
66
74
|
|
|
@@ -118,35 +126,66 @@ class AsyncWrappedMessages(AsyncMessages):
|
|
|
118
126
|
**kwargs: Any,
|
|
119
127
|
):
|
|
120
128
|
start_time = time.time()
|
|
121
|
-
usage_stats:
|
|
122
|
-
accumulated_content =
|
|
129
|
+
usage_stats: TokenUsage = TokenUsage(input_tokens=0, output_tokens=0)
|
|
130
|
+
accumulated_content = ""
|
|
131
|
+
content_blocks: List[StreamingContentBlock] = []
|
|
132
|
+
tools_in_progress: Dict[str, ToolInProgress] = {}
|
|
133
|
+
current_text_block: Optional[StreamingContentBlock] = None
|
|
123
134
|
response = await super().create(**kwargs)
|
|
124
135
|
|
|
125
136
|
async def generator():
|
|
126
137
|
nonlocal usage_stats
|
|
127
|
-
nonlocal accumulated_content
|
|
138
|
+
nonlocal accumulated_content
|
|
139
|
+
nonlocal content_blocks
|
|
140
|
+
nonlocal tools_in_progress
|
|
141
|
+
nonlocal current_text_block
|
|
142
|
+
|
|
128
143
|
try:
|
|
129
144
|
async for event in response:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
145
|
+
# Extract usage stats from event
|
|
146
|
+
event_usage = extract_anthropic_usage_from_event(event)
|
|
147
|
+
merge_usage_stats(usage_stats, event_usage)
|
|
148
|
+
|
|
149
|
+
# Handle content block start events
|
|
150
|
+
if hasattr(event, "type") and event.type == "content_block_start":
|
|
151
|
+
block, tool = handle_anthropic_content_block_start(event)
|
|
152
|
+
|
|
153
|
+
if block:
|
|
154
|
+
content_blocks.append(block)
|
|
155
|
+
|
|
156
|
+
if block.get("type") == "text":
|
|
157
|
+
current_text_block = block
|
|
158
|
+
else:
|
|
159
|
+
current_text_block = None
|
|
160
|
+
|
|
161
|
+
if tool:
|
|
162
|
+
tool_id = tool["block"].get("id")
|
|
163
|
+
if tool_id:
|
|
164
|
+
tools_in_progress[tool_id] = tool
|
|
165
|
+
|
|
166
|
+
# Handle text delta events
|
|
167
|
+
delta_text = handle_anthropic_text_delta(event, current_text_block)
|
|
168
|
+
|
|
169
|
+
if delta_text:
|
|
170
|
+
accumulated_content += delta_text
|
|
171
|
+
|
|
172
|
+
# Handle tool input delta events
|
|
173
|
+
handle_anthropic_tool_delta(
|
|
174
|
+
event, content_blocks, tools_in_progress
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# Handle content block stop events
|
|
178
|
+
if hasattr(event, "type") and event.type == "content_block_stop":
|
|
179
|
+
current_text_block = None
|
|
180
|
+
finalize_anthropic_tool_input(
|
|
181
|
+
event, content_blocks, tools_in_progress
|
|
182
|
+
)
|
|
143
183
|
|
|
144
184
|
yield event
|
|
145
185
|
|
|
146
186
|
finally:
|
|
147
187
|
end_time = time.time()
|
|
148
188
|
latency = end_time - start_time
|
|
149
|
-
output = "".join(accumulated_content)
|
|
150
189
|
|
|
151
190
|
await self._capture_streaming_event(
|
|
152
191
|
posthog_distinct_id,
|
|
@@ -157,7 +196,8 @@ class AsyncWrappedMessages(AsyncMessages):
|
|
|
157
196
|
kwargs,
|
|
158
197
|
usage_stats,
|
|
159
198
|
latency,
|
|
160
|
-
|
|
199
|
+
content_blocks,
|
|
200
|
+
accumulated_content,
|
|
161
201
|
)
|
|
162
202
|
|
|
163
203
|
return generator()
|
|
@@ -170,49 +210,39 @@ class AsyncWrappedMessages(AsyncMessages):
|
|
|
170
210
|
posthog_privacy_mode: bool,
|
|
171
211
|
posthog_groups: Optional[Dict[str, Any]],
|
|
172
212
|
kwargs: Dict[str, Any],
|
|
173
|
-
usage_stats:
|
|
213
|
+
usage_stats: TokenUsage,
|
|
174
214
|
latency: float,
|
|
175
|
-
|
|
215
|
+
content_blocks: List[StreamingContentBlock],
|
|
216
|
+
accumulated_content: str,
|
|
176
217
|
):
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
"
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
"$ai_output_tokens": usage_stats.get("output_tokens", 0),
|
|
197
|
-
"$ai_cache_read_input_tokens": usage_stats.get(
|
|
198
|
-
"cache_read_input_tokens", 0
|
|
199
|
-
),
|
|
200
|
-
"$ai_cache_creation_input_tokens": usage_stats.get(
|
|
201
|
-
"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
|
|
202
237
|
),
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
distinct_id=posthog_distinct_id or posthog_trace_id,
|
|
215
|
-
event="$ai_generation",
|
|
216
|
-
properties=event_properties,
|
|
217
|
-
groups=posthog_groups,
|
|
218
|
-
)
|
|
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)
|