Skip to content

Commit e85647b

Browse files
bernatixerclaude
andauthored
feat(ai): emit the served service tier as the explicit $ai_service_tier property (#932)
* feat(ai): emit the served tier as the explicit $ai_service_tier property Cost processing prices only from this property: its writers assert served values, unlike $ai_model_parameters.service_tier, which older releases populated from the request. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: add changeset Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent f04d44c commit e85647b

6 files changed

Lines changed: 28 additions & 10 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
pypi/posthog: minor
3+
---
4+
5+
OpenAI and LangChain generations now also emit the served service tier as the explicit `$ai_service_tier` event property, next to the copy inside `$ai_model_parameters`. Cost processing prices tiered calls only from the explicit property, whose writers assert response-derived values.

‎posthog/ai/langchain/callbacks.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,7 @@ def _capture_generation(
619619
):
620620
# The served tier comes from the response, because a requested tier can be refused.
621621
model_params = run.model_params
622+
served_tier = None
622623
if isinstance(output, LLMResult) and isinstance(output.llm_output, dict):
623624
served_tier = output.llm_output.get("service_tier")
624625
if served_tier is not None:
@@ -632,6 +633,8 @@ def _capture_generation(
632633
"$ai_provider": run.provider,
633634
"$ai_model": run.model,
634635
"$ai_model_parameters": model_params,
636+
# The explicit served-tier signal cost processing prices from.
637+
**({"$ai_service_tier": served_tier} if served_tier is not None else {}),
635638
"$ai_input": with_privacy_mode(
636639
self._ph_client,
637640
self._privacy_mode,

‎posthog/ai/utils.py‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -521,10 +521,11 @@ def call_llm_and_track_usage(
521521

522522
tag("$ai_provider", provider)
523523
tag("$ai_model", kwargs.get("model") or getattr(response, "model", None))
524-
tag(
525-
"$ai_model_parameters",
526-
get_model_params(kwargs, getattr(response, "service_tier", None)),
527-
)
524+
served_service_tier = getattr(response, "service_tier", None)
525+
tag("$ai_model_parameters", get_model_params(kwargs, served_service_tier))
526+
if served_service_tier is not None:
527+
# The explicit served-tier signal cost processing prices from.
528+
tag("$ai_service_tier", served_service_tier)
528529
tag(
529530
"$ai_input",
530531
with_privacy_mode(ph_client, posthog_privacy_mode, sanitized_messages),
@@ -687,10 +688,11 @@ async def call_llm_and_track_usage_async(
687688

688689
tag("$ai_provider", provider)
689690
tag("$ai_model", kwargs.get("model") or getattr(response, "model", None))
690-
tag(
691-
"$ai_model_parameters",
692-
get_model_params(kwargs, getattr(response, "service_tier", None)),
693-
)
691+
served_service_tier = getattr(response, "service_tier", None)
692+
tag("$ai_model_parameters", get_model_params(kwargs, served_service_tier))
693+
if served_service_tier is not None:
694+
# The explicit served-tier signal cost processing prices from.
695+
tag("$ai_service_tier", served_service_tier)
694696
tag(
695697
"$ai_input",
696698
with_privacy_mode(ph_client, posthog_privacy_mode, sanitized_messages),
@@ -840,6 +842,11 @@ def capture_streaming_event(
840842
"$ai_model_parameters": get_model_params(
841843
event_data["kwargs"], event_data.get("service_tier")
842844
),
845+
**(
846+
{"$ai_service_tier": event_data["service_tier"]}
847+
if event_data.get("service_tier") is not None
848+
else {}
849+
),
843850
"$ai_input": with_privacy_mode(
844851
ph_client,
845852
event_data["privacy_mode"],

‎posthog/test/ai/langchain/test_callbacks.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2864,6 +2864,7 @@ def test_served_service_tier_merges_into_model_parameters(mock_client):
28642864
props = mock_client.capture.call_args.kwargs["properties"]
28652865
assert props["$ai_model_parameters"]["service_tier"] == "flex"
28662866
assert props["$ai_model_parameters"]["temperature"] == 0.5
2867+
assert props["$ai_service_tier"] == "flex"
28672868

28682869

28692870
@pytest.mark.parametrize(

‎posthog/test/ai/openai/test_openai.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2683,6 +2683,7 @@ def test_served_service_tier_lands_in_model_parameters(
26832683

26842684
props = mock_client.capture.call_args[1]["properties"]
26852685
assert props["$ai_model_parameters"]["service_tier"] == "flex"
2686+
assert props["$ai_service_tier"] == "flex"
26862687

26872688

26882689
def test_response_without_service_tier_omits_it(mock_client, mock_openai_response):
@@ -2699,6 +2700,7 @@ def test_response_without_service_tier_omits_it(mock_client, mock_openai_respons
26992700

27002701
props = mock_client.capture.call_args[1]["properties"]
27012702
assert "service_tier" not in props["$ai_model_parameters"]
2703+
assert "$ai_service_tier" not in props
27022704

27032705

27042706
def test_streaming_state_tracks_served_service_tier():

‎posthog/test/test_utils.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ def test_guess_timezone_preserves_recent_naive_local_instant_as_utc(self):
112112
utc_now = datetime(2026, 1, 15, 12, 30, 45, 123456, tzinfo=timezone.utc)
113113

114114
with mock.patch("posthog.utils.datetime", wraps=datetime) as mock_datetime:
115-
mock_datetime.now.side_effect = (
116-
lambda tz=None: local_now if tz is None else utc_now.astimezone(tz)
115+
mock_datetime.now.side_effect = lambda tz=None: (
116+
local_now if tz is None else utc_now.astimezone(tz)
117117
)
118118
normalized = utils.guess_timezone(local_now)
119119

0 commit comments

Comments
 (0)