Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .sampo/changesets/doughty-iceseeker-tursas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
pypi/posthog: patch
---

Capture pre-calculated total cost from OpenAI Agents Responses API usage.
4 changes: 4 additions & 0 deletions posthog/ai/openai_agents/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,7 @@ def _handle_response_span(

# Try to extract usage from response
usage = getattr(response, "usage", None) if response else None
total_cost_usd = getattr(usage, "cost", None) if usage else None
input_tokens = 0
output_tokens = 0
if usage:
Expand All @@ -703,6 +704,9 @@ def _handle_response_span(
"$ai_total_tokens": input_tokens + output_tokens,
}

if total_cost_usd is not None:
properties["$ai_total_cost_usd"] = total_cost_usd

# Extract output content from response
if response:
output_items = getattr(response, "output", None)
Expand Down
24 changes: 24 additions & 0 deletions posthog/test/ai/openai_agents/test_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,7 @@ def test_response_span_with_output_and_total_tokens(
mock_response.usage = MagicMock()
mock_response.usage.input_tokens = 25
mock_response.usage.output_tokens = 10
mock_response.usage.cost = None

span_data = ResponseSpanData(
response=mock_response,
Expand All @@ -554,6 +555,29 @@ def test_response_span_with_output_and_total_tokens(
{"type": "message", "content": "Hello!"}
]
assert call_kwargs["properties"]["$ai_response_id"] == "resp_123"
assert "$ai_total_cost_usd" not in call_kwargs["properties"]

@pytest.mark.parametrize("total_cost_usd", [0.01234, 0.0])
def test_response_span_includes_total_cost(
self, processor, mock_client, mock_span, total_cost_usd
):
"""Test ResponseSpanData includes a pre-calculated total cost."""
mock_response = MagicMock()
mock_response.id = "resp_123"
mock_response.model = "gpt-4o"
mock_response.output = []
mock_response.usage = MagicMock()
mock_response.usage.input_tokens = 25
mock_response.usage.output_tokens = 10
mock_response.usage.cost = total_cost_usd

mock_span.span_data = ResponseSpanData(response=mock_response)

processor.on_span_start(mock_span)
processor.on_span_end(mock_span)

call_kwargs = mock_client.capture.call_args[1]
assert call_kwargs["properties"]["$ai_total_cost_usd"] == total_cost_usd

def test_speech_span_with_pass_through_properties(
self, processor, mock_client, mock_span
Expand Down