|
13 | 13 | from posthog.client import Client as PostHogClient |
14 | 14 |
|
15 | 15 |
|
| 16 | +_TOKEN_PROPERTY_KEYS = frozenset( |
| 17 | + { |
| 18 | + "$ai_input_tokens", |
| 19 | + "$ai_output_tokens", |
| 20 | + "$ai_cache_read_input_tokens", |
| 21 | + "$ai_cache_creation_input_tokens", |
| 22 | + "$ai_total_tokens", |
| 23 | + "$ai_reasoning_tokens", |
| 24 | + } |
| 25 | +) |
| 26 | + |
| 27 | + |
| 28 | +def _get_tokens_source( |
| 29 | + sdk_tags: Dict[str, Any], posthog_properties: Optional[Dict[str, Any]] |
| 30 | +) -> str: |
| 31 | + if posthog_properties and any( |
| 32 | + key in posthog_properties for key in _TOKEN_PROPERTY_KEYS |
| 33 | + ): |
| 34 | + return "passthrough" |
| 35 | + return "sdk" |
| 36 | + |
| 37 | + |
16 | 38 | def serialize_raw_usage(raw_usage: Any) -> Optional[Dict[str, Any]]: |
17 | 39 | """ |
18 | 40 | Convert raw provider usage objects to JSON-serializable dicts. |
@@ -413,14 +435,19 @@ def call_llm_and_track_usage( |
413 | 435 |
|
414 | 436 | # send the event to posthog |
415 | 437 | if hasattr(ph_client, "capture") and callable(ph_client.capture): |
| 438 | + sdk_tags = get_tags() |
| 439 | + merged_properties = { |
| 440 | + **sdk_tags, |
| 441 | + **(posthog_properties or {}), |
| 442 | + **(error_params or {}), |
| 443 | + } |
| 444 | + merged_properties["$ai_tokens_source"] = _get_tokens_source( |
| 445 | + sdk_tags, posthog_properties |
| 446 | + ) |
416 | 447 | ph_client.capture( |
417 | 448 | distinct_id=posthog_distinct_id or posthog_trace_id, |
418 | 449 | event="$ai_generation", |
419 | | - properties={ |
420 | | - **get_tags(), |
421 | | - **(posthog_properties or {}), |
422 | | - **(error_params or {}), |
423 | | - }, |
| 450 | + properties=merged_properties, |
424 | 451 | groups=posthog_groups, |
425 | 452 | ) |
426 | 453 |
|
@@ -543,14 +570,19 @@ async def call_llm_and_track_usage_async( |
543 | 570 |
|
544 | 571 | # send the event to posthog |
545 | 572 | if hasattr(ph_client, "capture") and callable(ph_client.capture): |
| 573 | + sdk_tags = get_tags() |
| 574 | + merged_properties = { |
| 575 | + **sdk_tags, |
| 576 | + **(posthog_properties or {}), |
| 577 | + **(error_params or {}), |
| 578 | + } |
| 579 | + merged_properties["$ai_tokens_source"] = _get_tokens_source( |
| 580 | + sdk_tags, posthog_properties |
| 581 | + ) |
546 | 582 | ph_client.capture( |
547 | 583 | distinct_id=posthog_distinct_id or posthog_trace_id, |
548 | 584 | event="$ai_generation", |
549 | | - properties={ |
550 | | - **get_tags(), |
551 | | - **(posthog_properties or {}), |
552 | | - **(error_params or {}), |
553 | | - }, |
| 585 | + properties=merged_properties, |
554 | 586 | groups=posthog_groups, |
555 | 587 | ) |
556 | 588 |
|
@@ -627,6 +659,15 @@ def capture_streaming_event( |
627 | 659 | **(event_data.get("properties") or {}), |
628 | 660 | } |
629 | 661 |
|
| 662 | + # Determine token source: SDK-computed vs externally overridden |
| 663 | + sdk_token_tags = { |
| 664 | + "$ai_input_tokens": event_data["usage_stats"].get("input_tokens", 0), |
| 665 | + "$ai_output_tokens": event_data["usage_stats"].get("output_tokens", 0), |
| 666 | + } |
| 667 | + event_properties["$ai_tokens_source"] = _get_tokens_source( |
| 668 | + sdk_token_tags, event_data.get("properties") |
| 669 | + ) |
| 670 | + |
630 | 671 | # Extract and add tools based on provider |
631 | 672 | available_tools = extract_available_tool_calls( |
632 | 673 | event_data["provider"], |
|
0 commit comments