posthoganalytics 6.7.0__py3-none-any.whl → 6.7.2__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/ai/anthropic/__init__.py +10 -0
- posthoganalytics/ai/anthropic/anthropic.py +94 -63
- posthoganalytics/ai/anthropic/anthropic_async.py +88 -21
- posthoganalytics/ai/anthropic/anthropic_converter.py +393 -0
- posthoganalytics/ai/gemini/__init__.py +12 -1
- posthoganalytics/ai/gemini/gemini.py +61 -67
- posthoganalytics/ai/gemini/gemini_converter.py +438 -0
- posthoganalytics/ai/langchain/callbacks.py +3 -2
- posthoganalytics/ai/openai/__init__.py +16 -1
- posthoganalytics/ai/openai/openai.py +114 -148
- posthoganalytics/ai/openai/openai_async.py +72 -73
- posthoganalytics/ai/openai/openai_converter.py +585 -0
- posthoganalytics/ai/sanitization.py +226 -0
- posthoganalytics/ai/types.py +142 -0
- posthoganalytics/ai/utils.py +232 -255
- posthoganalytics/client.py +7 -7
- posthoganalytics/test/test_feature_flags.py +2 -2
- posthoganalytics/version.py +1 -1
- {posthoganalytics-6.7.0.dist-info → posthoganalytics-6.7.2.dist-info}/METADATA +1 -1
- {posthoganalytics-6.7.0.dist-info → posthoganalytics-6.7.2.dist-info}/RECORD +23 -18
- {posthoganalytics-6.7.0.dist-info → posthoganalytics-6.7.2.dist-info}/WHEEL +0 -0
- {posthoganalytics-6.7.0.dist-info → posthoganalytics-6.7.2.dist-info}/licenses/LICENSE +0 -0
- {posthoganalytics-6.7.0.dist-info → posthoganalytics-6.7.2.dist-info}/top_level.txt +0 -0
|
@@ -12,9 +12,16 @@ except ImportError:
|
|
|
12
12
|
from posthoganalytics.ai.utils import (
|
|
13
13
|
call_llm_and_track_usage,
|
|
14
14
|
extract_available_tool_calls,
|
|
15
|
-
|
|
15
|
+
merge_usage_stats,
|
|
16
16
|
with_privacy_mode,
|
|
17
17
|
)
|
|
18
|
+
from posthoganalytics.ai.openai.openai_converter import (
|
|
19
|
+
extract_openai_usage_from_chunk,
|
|
20
|
+
extract_openai_content_from_chunk,
|
|
21
|
+
extract_openai_tool_calls_from_chunk,
|
|
22
|
+
accumulate_openai_tool_calls,
|
|
23
|
+
)
|
|
24
|
+
from posthoganalytics.ai.sanitization import sanitize_openai, sanitize_openai_response
|
|
18
25
|
from posthoganalytics.client import Client as PostHogClient
|
|
19
26
|
from posthoganalytics import setup
|
|
20
27
|
|
|
@@ -33,6 +40,7 @@ class OpenAI(openai.OpenAI):
|
|
|
33
40
|
posthog_client: If provided, events will be captured via this client instead of the global `posthog`.
|
|
34
41
|
**openai_config: Any additional keyword args to set on openai (e.g. organization="xxx").
|
|
35
42
|
"""
|
|
43
|
+
|
|
36
44
|
super().__init__(**kwargs)
|
|
37
45
|
self._ph_client = posthog_client or setup()
|
|
38
46
|
|
|
@@ -122,35 +130,17 @@ class WrappedResponses:
|
|
|
122
130
|
|
|
123
131
|
try:
|
|
124
132
|
for chunk in response:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
"total_tokens",
|
|
137
|
-
]
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
# Add support for cached tokens
|
|
141
|
-
if hasattr(chunk.usage, "output_tokens_details") and hasattr(
|
|
142
|
-
chunk.usage.output_tokens_details, "reasoning_tokens"
|
|
143
|
-
):
|
|
144
|
-
usage_stats["reasoning_tokens"] = (
|
|
145
|
-
chunk.usage.output_tokens_details.reasoning_tokens
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
if hasattr(chunk.usage, "input_tokens_details") and hasattr(
|
|
149
|
-
chunk.usage.input_tokens_details, "cached_tokens"
|
|
150
|
-
):
|
|
151
|
-
usage_stats["cache_read_input_tokens"] = (
|
|
152
|
-
chunk.usage.input_tokens_details.cached_tokens
|
|
153
|
-
)
|
|
133
|
+
# Extract usage stats from chunk
|
|
134
|
+
chunk_usage = extract_openai_usage_from_chunk(chunk, "responses")
|
|
135
|
+
|
|
136
|
+
if chunk_usage:
|
|
137
|
+
merge_usage_stats(usage_stats, chunk_usage)
|
|
138
|
+
|
|
139
|
+
# Extract content from chunk
|
|
140
|
+
content = extract_openai_content_from_chunk(chunk, "responses")
|
|
141
|
+
|
|
142
|
+
if content is not None:
|
|
143
|
+
final_content.append(content)
|
|
154
144
|
|
|
155
145
|
yield chunk
|
|
156
146
|
|
|
@@ -168,7 +158,7 @@ class WrappedResponses:
|
|
|
168
158
|
usage_stats,
|
|
169
159
|
latency,
|
|
170
160
|
output,
|
|
171
|
-
|
|
161
|
+
None, # Responses API doesn't have tools
|
|
172
162
|
)
|
|
173
163
|
|
|
174
164
|
return generator()
|
|
@@ -186,47 +176,36 @@ class WrappedResponses:
|
|
|
186
176
|
output: Any,
|
|
187
177
|
available_tool_calls: Optional[List[Dict[str, Any]]] = None,
|
|
188
178
|
):
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
),
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
"
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if available_tool_calls:
|
|
218
|
-
event_properties["$ai_tools"] = available_tool_calls
|
|
179
|
+
from posthoganalytics.ai.types import StreamingEventData
|
|
180
|
+
from posthoganalytics.ai.openai.openai_converter import (
|
|
181
|
+
standardize_openai_usage,
|
|
182
|
+
format_openai_streaming_input,
|
|
183
|
+
format_openai_streaming_output,
|
|
184
|
+
)
|
|
185
|
+
from posthoganalytics.ai.utils import capture_streaming_event
|
|
186
|
+
|
|
187
|
+
# Prepare standardized event data
|
|
188
|
+
formatted_input = format_openai_streaming_input(kwargs, "responses")
|
|
189
|
+
sanitized_input = sanitize_openai_response(formatted_input)
|
|
190
|
+
|
|
191
|
+
event_data = StreamingEventData(
|
|
192
|
+
provider="openai",
|
|
193
|
+
model=kwargs.get("model", "unknown"),
|
|
194
|
+
base_url=str(self._client.base_url),
|
|
195
|
+
kwargs=kwargs,
|
|
196
|
+
formatted_input=sanitized_input,
|
|
197
|
+
formatted_output=format_openai_streaming_output(output, "responses"),
|
|
198
|
+
usage_stats=standardize_openai_usage(usage_stats, "responses"),
|
|
199
|
+
latency=latency,
|
|
200
|
+
distinct_id=posthog_distinct_id,
|
|
201
|
+
trace_id=posthog_trace_id,
|
|
202
|
+
properties=posthog_properties,
|
|
203
|
+
privacy_mode=posthog_privacy_mode,
|
|
204
|
+
groups=posthog_groups,
|
|
205
|
+
)
|
|
219
206
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if hasattr(self._client._ph_client, "capture"):
|
|
224
|
-
self._client._ph_client.capture(
|
|
225
|
-
distinct_id=posthog_distinct_id or posthog_trace_id,
|
|
226
|
-
event="$ai_generation",
|
|
227
|
-
properties=event_properties,
|
|
228
|
-
groups=posthog_groups,
|
|
229
|
-
)
|
|
207
|
+
# Use the common capture function
|
|
208
|
+
capture_streaming_event(self._client._ph_client, event_data)
|
|
230
209
|
|
|
231
210
|
def parse(
|
|
232
211
|
self,
|
|
@@ -339,6 +318,7 @@ class WrappedCompletions:
|
|
|
339
318
|
start_time = time.time()
|
|
340
319
|
usage_stats: Dict[str, int] = {}
|
|
341
320
|
accumulated_content = []
|
|
321
|
+
accumulated_tool_calls: Dict[int, Dict[str, Any]] = {}
|
|
342
322
|
if "stream_options" not in kwargs:
|
|
343
323
|
kwargs["stream_options"] = {}
|
|
344
324
|
kwargs["stream_options"]["include_usage"] = True
|
|
@@ -347,50 +327,42 @@ class WrappedCompletions:
|
|
|
347
327
|
def generator():
|
|
348
328
|
nonlocal usage_stats
|
|
349
329
|
nonlocal accumulated_content # noqa: F824
|
|
330
|
+
nonlocal accumulated_tool_calls
|
|
350
331
|
|
|
351
332
|
try:
|
|
352
333
|
for chunk in response:
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
if hasattr(chunk.usage, "output_tokens_details") and hasattr(
|
|
372
|
-
chunk.usage.output_tokens_details, "reasoning_tokens"
|
|
373
|
-
):
|
|
374
|
-
usage_stats["reasoning_tokens"] = (
|
|
375
|
-
chunk.usage.output_tokens_details.reasoning_tokens
|
|
376
|
-
)
|
|
377
|
-
|
|
378
|
-
if (
|
|
379
|
-
hasattr(chunk, "choices")
|
|
380
|
-
and chunk.choices
|
|
381
|
-
and len(chunk.choices) > 0
|
|
382
|
-
):
|
|
383
|
-
if chunk.choices[0].delta and chunk.choices[0].delta.content:
|
|
384
|
-
content = chunk.choices[0].delta.content
|
|
385
|
-
if content:
|
|
386
|
-
accumulated_content.append(content)
|
|
334
|
+
# Extract usage stats from chunk
|
|
335
|
+
chunk_usage = extract_openai_usage_from_chunk(chunk, "chat")
|
|
336
|
+
|
|
337
|
+
if chunk_usage:
|
|
338
|
+
merge_usage_stats(usage_stats, chunk_usage)
|
|
339
|
+
|
|
340
|
+
# Extract content from chunk
|
|
341
|
+
content = extract_openai_content_from_chunk(chunk, "chat")
|
|
342
|
+
|
|
343
|
+
if content is not None:
|
|
344
|
+
accumulated_content.append(content)
|
|
345
|
+
|
|
346
|
+
# Extract and accumulate tool calls from chunk
|
|
347
|
+
chunk_tool_calls = extract_openai_tool_calls_from_chunk(chunk)
|
|
348
|
+
if chunk_tool_calls:
|
|
349
|
+
accumulate_openai_tool_calls(
|
|
350
|
+
accumulated_tool_calls, chunk_tool_calls
|
|
351
|
+
)
|
|
387
352
|
|
|
388
353
|
yield chunk
|
|
389
354
|
|
|
390
355
|
finally:
|
|
391
356
|
end_time = time.time()
|
|
392
357
|
latency = end_time - start_time
|
|
393
|
-
|
|
358
|
+
|
|
359
|
+
# Convert accumulated tool calls dict to list
|
|
360
|
+
tool_calls_list = (
|
|
361
|
+
list(accumulated_tool_calls.values())
|
|
362
|
+
if accumulated_tool_calls
|
|
363
|
+
else None
|
|
364
|
+
)
|
|
365
|
+
|
|
394
366
|
self._capture_streaming_event(
|
|
395
367
|
posthog_distinct_id,
|
|
396
368
|
posthog_trace_id,
|
|
@@ -400,7 +372,8 @@ class WrappedCompletions:
|
|
|
400
372
|
kwargs,
|
|
401
373
|
usage_stats,
|
|
402
374
|
latency,
|
|
403
|
-
|
|
375
|
+
accumulated_content,
|
|
376
|
+
tool_calls_list,
|
|
404
377
|
extract_available_tool_calls("openai", kwargs),
|
|
405
378
|
)
|
|
406
379
|
|
|
@@ -417,49 +390,39 @@ class WrappedCompletions:
|
|
|
417
390
|
usage_stats: Dict[str, int],
|
|
418
391
|
latency: float,
|
|
419
392
|
output: Any,
|
|
393
|
+
tool_calls: Optional[List[Dict[str, Any]]] = None,
|
|
420
394
|
available_tool_calls: Optional[List[Dict[str, Any]]] = None,
|
|
421
395
|
):
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
),
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
"
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
396
|
+
from posthoganalytics.ai.types import StreamingEventData
|
|
397
|
+
from posthoganalytics.ai.openai.openai_converter import (
|
|
398
|
+
standardize_openai_usage,
|
|
399
|
+
format_openai_streaming_input,
|
|
400
|
+
format_openai_streaming_output,
|
|
401
|
+
)
|
|
402
|
+
from posthoganalytics.ai.utils import capture_streaming_event
|
|
403
|
+
|
|
404
|
+
# Prepare standardized event data
|
|
405
|
+
formatted_input = format_openai_streaming_input(kwargs, "chat")
|
|
406
|
+
sanitized_input = sanitize_openai(formatted_input)
|
|
407
|
+
|
|
408
|
+
event_data = StreamingEventData(
|
|
409
|
+
provider="openai",
|
|
410
|
+
model=kwargs.get("model", "unknown"),
|
|
411
|
+
base_url=str(self._client.base_url),
|
|
412
|
+
kwargs=kwargs,
|
|
413
|
+
formatted_input=sanitized_input,
|
|
414
|
+
formatted_output=format_openai_streaming_output(output, "chat", tool_calls),
|
|
415
|
+
usage_stats=standardize_openai_usage(usage_stats, "chat"),
|
|
416
|
+
latency=latency,
|
|
417
|
+
distinct_id=posthog_distinct_id,
|
|
418
|
+
trace_id=posthog_trace_id,
|
|
419
|
+
properties=posthog_properties,
|
|
420
|
+
privacy_mode=posthog_privacy_mode,
|
|
421
|
+
groups=posthog_groups,
|
|
422
|
+
)
|
|
449
423
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
if posthog_distinct_id is None:
|
|
454
|
-
event_properties["$process_person_profile"] = False
|
|
455
|
-
|
|
456
|
-
if hasattr(self._client._ph_client, "capture"):
|
|
457
|
-
self._client._ph_client.capture(
|
|
458
|
-
distinct_id=posthog_distinct_id or posthog_trace_id,
|
|
459
|
-
event="$ai_generation",
|
|
460
|
-
properties=event_properties,
|
|
461
|
-
groups=posthog_groups,
|
|
462
|
-
)
|
|
424
|
+
# Use the common capture function
|
|
425
|
+
capture_streaming_event(self._client._ph_client, event_data)
|
|
463
426
|
|
|
464
427
|
|
|
465
428
|
class WrappedEmbeddings:
|
|
@@ -496,6 +459,7 @@ class WrappedEmbeddings:
|
|
|
496
459
|
Returns:
|
|
497
460
|
The response from OpenAI's embeddings.create call.
|
|
498
461
|
"""
|
|
462
|
+
|
|
499
463
|
if posthog_trace_id is None:
|
|
500
464
|
posthog_trace_id = str(uuid.uuid4())
|
|
501
465
|
|
|
@@ -518,7 +482,9 @@ class WrappedEmbeddings:
|
|
|
518
482
|
"$ai_provider": "openai",
|
|
519
483
|
"$ai_model": kwargs.get("model"),
|
|
520
484
|
"$ai_input": with_privacy_mode(
|
|
521
|
-
self._client._ph_client,
|
|
485
|
+
self._client._ph_client,
|
|
486
|
+
posthog_privacy_mode,
|
|
487
|
+
sanitize_openai_response(kwargs.get("input")),
|
|
522
488
|
),
|
|
523
489
|
"$ai_http_status": 200,
|
|
524
490
|
"$ai_input_tokens": usage_stats.get("prompt_tokens", 0),
|
|
@@ -14,8 +14,17 @@ from posthoganalytics.ai.utils import (
|
|
|
14
14
|
call_llm_and_track_usage_async,
|
|
15
15
|
extract_available_tool_calls,
|
|
16
16
|
get_model_params,
|
|
17
|
+
merge_usage_stats,
|
|
17
18
|
with_privacy_mode,
|
|
18
19
|
)
|
|
20
|
+
from posthoganalytics.ai.openai.openai_converter import (
|
|
21
|
+
extract_openai_usage_from_chunk,
|
|
22
|
+
extract_openai_content_from_chunk,
|
|
23
|
+
extract_openai_tool_calls_from_chunk,
|
|
24
|
+
accumulate_openai_tool_calls,
|
|
25
|
+
format_openai_streaming_output,
|
|
26
|
+
)
|
|
27
|
+
from posthoganalytics.ai.sanitization import sanitize_openai, sanitize_openai_response
|
|
19
28
|
from posthoganalytics.client import Client as PostHogClient
|
|
20
29
|
|
|
21
30
|
|
|
@@ -34,6 +43,7 @@ class AsyncOpenAI(openai.AsyncOpenAI):
|
|
|
34
43
|
of the global posthog.
|
|
35
44
|
**openai_config: Any additional keyword args to set on openai (e.g. organization="xxx").
|
|
36
45
|
"""
|
|
46
|
+
|
|
37
47
|
super().__init__(**kwargs)
|
|
38
48
|
self._ph_client = posthog_client or setup()
|
|
39
49
|
|
|
@@ -66,6 +76,7 @@ class WrappedResponses:
|
|
|
66
76
|
|
|
67
77
|
def __getattr__(self, name):
|
|
68
78
|
"""Fallback to original responses object for any methods we don't explicitly handle."""
|
|
79
|
+
|
|
69
80
|
return getattr(self._original, name)
|
|
70
81
|
|
|
71
82
|
async def create(
|
|
@@ -115,7 +126,7 @@ class WrappedResponses:
|
|
|
115
126
|
start_time = time.time()
|
|
116
127
|
usage_stats: Dict[str, int] = {}
|
|
117
128
|
final_content = []
|
|
118
|
-
response =
|
|
129
|
+
response = self._original.create(**kwargs)
|
|
119
130
|
|
|
120
131
|
async def async_generator():
|
|
121
132
|
nonlocal usage_stats
|
|
@@ -123,35 +134,17 @@ class WrappedResponses:
|
|
|
123
134
|
|
|
124
135
|
try:
|
|
125
136
|
async for chunk in response:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
"total_tokens",
|
|
138
|
-
]
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
# Add support for cached tokens
|
|
142
|
-
if hasattr(chunk.usage, "output_tokens_details") and hasattr(
|
|
143
|
-
chunk.usage.output_tokens_details, "reasoning_tokens"
|
|
144
|
-
):
|
|
145
|
-
usage_stats["reasoning_tokens"] = (
|
|
146
|
-
chunk.usage.output_tokens_details.reasoning_tokens
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
if hasattr(chunk.usage, "input_tokens_details") and hasattr(
|
|
150
|
-
chunk.usage.input_tokens_details, "cached_tokens"
|
|
151
|
-
):
|
|
152
|
-
usage_stats["cache_read_input_tokens"] = (
|
|
153
|
-
chunk.usage.input_tokens_details.cached_tokens
|
|
154
|
-
)
|
|
137
|
+
# Extract usage stats from chunk
|
|
138
|
+
chunk_usage = extract_openai_usage_from_chunk(chunk, "responses")
|
|
139
|
+
|
|
140
|
+
if chunk_usage:
|
|
141
|
+
merge_usage_stats(usage_stats, chunk_usage)
|
|
142
|
+
|
|
143
|
+
# Extract content from chunk
|
|
144
|
+
content = extract_openai_content_from_chunk(chunk, "responses")
|
|
145
|
+
|
|
146
|
+
if content is not None:
|
|
147
|
+
final_content.append(content)
|
|
155
148
|
|
|
156
149
|
yield chunk
|
|
157
150
|
|
|
@@ -159,6 +152,7 @@ class WrappedResponses:
|
|
|
159
152
|
end_time = time.time()
|
|
160
153
|
latency = end_time - start_time
|
|
161
154
|
output = final_content
|
|
155
|
+
|
|
162
156
|
await self._capture_streaming_event(
|
|
163
157
|
posthog_distinct_id,
|
|
164
158
|
posthog_trace_id,
|
|
@@ -195,12 +189,14 @@ class WrappedResponses:
|
|
|
195
189
|
"$ai_model": kwargs.get("model"),
|
|
196
190
|
"$ai_model_parameters": get_model_params(kwargs),
|
|
197
191
|
"$ai_input": with_privacy_mode(
|
|
198
|
-
self._client._ph_client,
|
|
192
|
+
self._client._ph_client,
|
|
193
|
+
posthog_privacy_mode,
|
|
194
|
+
sanitize_openai_response(kwargs.get("input")),
|
|
199
195
|
),
|
|
200
196
|
"$ai_output_choices": with_privacy_mode(
|
|
201
197
|
self._client._ph_client,
|
|
202
198
|
posthog_privacy_mode,
|
|
203
|
-
output,
|
|
199
|
+
format_openai_streaming_output(output, "responses"),
|
|
204
200
|
),
|
|
205
201
|
"$ai_http_status": 200,
|
|
206
202
|
"$ai_input_tokens": usage_stats.get("input_tokens", 0),
|
|
@@ -342,59 +338,50 @@ class WrappedCompletions:
|
|
|
342
338
|
start_time = time.time()
|
|
343
339
|
usage_stats: Dict[str, int] = {}
|
|
344
340
|
accumulated_content = []
|
|
341
|
+
accumulated_tool_calls: Dict[int, Dict[str, Any]] = {}
|
|
345
342
|
|
|
346
343
|
if "stream_options" not in kwargs:
|
|
347
344
|
kwargs["stream_options"] = {}
|
|
348
345
|
kwargs["stream_options"]["include_usage"] = True
|
|
349
|
-
response =
|
|
346
|
+
response = self._original.create(**kwargs)
|
|
350
347
|
|
|
351
348
|
async def async_generator():
|
|
352
349
|
nonlocal usage_stats
|
|
353
350
|
nonlocal accumulated_content # noqa: F824
|
|
351
|
+
nonlocal accumulated_tool_calls
|
|
354
352
|
|
|
355
353
|
try:
|
|
356
354
|
async for chunk in response:
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
)
|
|
374
|
-
|
|
375
|
-
if hasattr(chunk.usage, "output_tokens_details") and hasattr(
|
|
376
|
-
chunk.usage.output_tokens_details, "reasoning_tokens"
|
|
377
|
-
):
|
|
378
|
-
usage_stats["reasoning_tokens"] = (
|
|
379
|
-
chunk.usage.output_tokens_details.reasoning_tokens
|
|
380
|
-
)
|
|
381
|
-
|
|
382
|
-
if (
|
|
383
|
-
hasattr(chunk, "choices")
|
|
384
|
-
and chunk.choices
|
|
385
|
-
and len(chunk.choices) > 0
|
|
386
|
-
):
|
|
387
|
-
if chunk.choices[0].delta and chunk.choices[0].delta.content:
|
|
388
|
-
content = chunk.choices[0].delta.content
|
|
389
|
-
if content:
|
|
390
|
-
accumulated_content.append(content)
|
|
355
|
+
# Extract usage stats from chunk
|
|
356
|
+
chunk_usage = extract_openai_usage_from_chunk(chunk, "chat")
|
|
357
|
+
if chunk_usage:
|
|
358
|
+
merge_usage_stats(usage_stats, chunk_usage)
|
|
359
|
+
|
|
360
|
+
# Extract content from chunk
|
|
361
|
+
content = extract_openai_content_from_chunk(chunk, "chat")
|
|
362
|
+
if content is not None:
|
|
363
|
+
accumulated_content.append(content)
|
|
364
|
+
|
|
365
|
+
# Extract and accumulate tool calls from chunk
|
|
366
|
+
chunk_tool_calls = extract_openai_tool_calls_from_chunk(chunk)
|
|
367
|
+
if chunk_tool_calls:
|
|
368
|
+
accumulate_openai_tool_calls(
|
|
369
|
+
accumulated_tool_calls, chunk_tool_calls
|
|
370
|
+
)
|
|
391
371
|
|
|
392
372
|
yield chunk
|
|
393
373
|
|
|
394
374
|
finally:
|
|
395
375
|
end_time = time.time()
|
|
396
376
|
latency = end_time - start_time
|
|
397
|
-
|
|
377
|
+
|
|
378
|
+
# Convert accumulated tool calls dict to list
|
|
379
|
+
tool_calls_list = (
|
|
380
|
+
list(accumulated_tool_calls.values())
|
|
381
|
+
if accumulated_tool_calls
|
|
382
|
+
else None
|
|
383
|
+
)
|
|
384
|
+
|
|
398
385
|
await self._capture_streaming_event(
|
|
399
386
|
posthog_distinct_id,
|
|
400
387
|
posthog_trace_id,
|
|
@@ -404,7 +391,8 @@ class WrappedCompletions:
|
|
|
404
391
|
kwargs,
|
|
405
392
|
usage_stats,
|
|
406
393
|
latency,
|
|
407
|
-
|
|
394
|
+
accumulated_content,
|
|
395
|
+
tool_calls_list,
|
|
408
396
|
extract_available_tool_calls("openai", kwargs),
|
|
409
397
|
)
|
|
410
398
|
|
|
@@ -421,6 +409,7 @@ class WrappedCompletions:
|
|
|
421
409
|
usage_stats: Dict[str, int],
|
|
422
410
|
latency: float,
|
|
423
411
|
output: Any,
|
|
412
|
+
tool_calls: Optional[List[Dict[str, Any]]] = None,
|
|
424
413
|
available_tool_calls: Optional[List[Dict[str, Any]]] = None,
|
|
425
414
|
):
|
|
426
415
|
if posthog_trace_id is None:
|
|
@@ -431,12 +420,14 @@ class WrappedCompletions:
|
|
|
431
420
|
"$ai_model": kwargs.get("model"),
|
|
432
421
|
"$ai_model_parameters": get_model_params(kwargs),
|
|
433
422
|
"$ai_input": with_privacy_mode(
|
|
434
|
-
self._client._ph_client,
|
|
423
|
+
self._client._ph_client,
|
|
424
|
+
posthog_privacy_mode,
|
|
425
|
+
sanitize_openai(kwargs.get("messages")),
|
|
435
426
|
),
|
|
436
427
|
"$ai_output_choices": with_privacy_mode(
|
|
437
428
|
self._client._ph_client,
|
|
438
429
|
posthog_privacy_mode,
|
|
439
|
-
|
|
430
|
+
format_openai_streaming_output(output, "chat", tool_calls),
|
|
440
431
|
),
|
|
441
432
|
"$ai_http_status": 200,
|
|
442
433
|
"$ai_input_tokens": usage_stats.get("prompt_tokens", 0),
|
|
@@ -475,6 +466,7 @@ class WrappedEmbeddings:
|
|
|
475
466
|
|
|
476
467
|
def __getattr__(self, name):
|
|
477
468
|
"""Fallback to original embeddings object for any methods we don't explicitly handle."""
|
|
469
|
+
|
|
478
470
|
return getattr(self._original, name)
|
|
479
471
|
|
|
480
472
|
async def create(
|
|
@@ -500,15 +492,17 @@ class WrappedEmbeddings:
|
|
|
500
492
|
Returns:
|
|
501
493
|
The response from OpenAI's embeddings.create call.
|
|
502
494
|
"""
|
|
495
|
+
|
|
503
496
|
if posthog_trace_id is None:
|
|
504
497
|
posthog_trace_id = str(uuid.uuid4())
|
|
505
498
|
|
|
506
499
|
start_time = time.time()
|
|
507
|
-
response =
|
|
500
|
+
response = self._original.create(**kwargs)
|
|
508
501
|
end_time = time.time()
|
|
509
502
|
|
|
510
503
|
# Extract usage statistics if available
|
|
511
504
|
usage_stats = {}
|
|
505
|
+
|
|
512
506
|
if hasattr(response, "usage") and response.usage:
|
|
513
507
|
usage_stats = {
|
|
514
508
|
"prompt_tokens": getattr(response.usage, "prompt_tokens", 0),
|
|
@@ -522,7 +516,9 @@ class WrappedEmbeddings:
|
|
|
522
516
|
"$ai_provider": "openai",
|
|
523
517
|
"$ai_model": kwargs.get("model"),
|
|
524
518
|
"$ai_input": with_privacy_mode(
|
|
525
|
-
self._client._ph_client,
|
|
519
|
+
self._client._ph_client,
|
|
520
|
+
posthog_privacy_mode,
|
|
521
|
+
sanitize_openai_response(kwargs.get("input")),
|
|
526
522
|
),
|
|
527
523
|
"$ai_http_status": 200,
|
|
528
524
|
"$ai_input_tokens": usage_stats.get("prompt_tokens", 0),
|
|
@@ -556,6 +552,7 @@ class WrappedBeta:
|
|
|
556
552
|
|
|
557
553
|
def __getattr__(self, name):
|
|
558
554
|
"""Fallback to original beta object for any methods we don't explicitly handle."""
|
|
555
|
+
|
|
559
556
|
return getattr(self._original, name)
|
|
560
557
|
|
|
561
558
|
@property
|
|
@@ -572,6 +569,7 @@ class WrappedBetaChat:
|
|
|
572
569
|
|
|
573
570
|
def __getattr__(self, name):
|
|
574
571
|
"""Fallback to original beta chat object for any methods we don't explicitly handle."""
|
|
572
|
+
|
|
575
573
|
return getattr(self._original, name)
|
|
576
574
|
|
|
577
575
|
@property
|
|
@@ -588,6 +586,7 @@ class WrappedBetaCompletions:
|
|
|
588
586
|
|
|
589
587
|
def __getattr__(self, name):
|
|
590
588
|
"""Fallback to original beta completions object for any methods we don't explicitly handle."""
|
|
589
|
+
|
|
591
590
|
return getattr(self._original, name)
|
|
592
591
|
|
|
593
592
|
async def parse(
|