Skip to content

Commit 1875b71

Browse files
authored
feat(ai): add OpenAI Agents SDK integration (#408)
* feat(ai): add OpenAI Agents SDK integration Add PostHogTracingProcessor that implements the OpenAI Agents SDK TracingProcessor interface to capture agent traces in PostHog. - Maps GenerationSpanData to $ai_generation events - Maps FunctionSpanData, AgentSpanData, HandoffSpanData, GuardrailSpanData to $ai_span events with appropriate types - Supports privacy mode, groups, and custom properties - Includes instrument() helper for one-liner setup - 22 unit tests covering all span types * feat(openai-agents): add $ai_group_id support for linking conversation traces - Capture group_id from trace and include as $ai_group_id on all events - Add _get_group_id() helper to retrieve group_id from trace metadata - Pass group_id through all span handlers (generation, function, agent, handoff, guardrail, response, custom, audio, mcp, generic) - Enables linking multiple traces in the same conversation thread * feat(openai-agents): add enhanced span properties - Add $ai_total_tokens to generation and response spans (required by PostHog cost reporting) - Add $ai_error_type for cross-provider error categorization (model_behavior_error, user_error, input_guardrail_triggered, output_guardrail_triggered, max_turns_exceeded) - Add $ai_output_choices to response spans for output content capture - Add audio pass-through properties for voice spans: - first_content_at (time to first audio byte) - audio_input_format / audio_output_format - model_config - $ai_input for TTS text input - Add comprehensive tests for all new properties * Add $ai_framework property and standardize $ai_provider for OpenAI Agents - Add $ai_framework="openai-agents" to all events for framework identification - Standardize $ai_provider="openai" on all events (previously some used "openai_agents") - Follows pattern from posthog-js where $ai_provider is the underlying LLM provider * chore: bump version to 7.7.0 for OpenAI Agents SDK integration * fix: add openai_agents package to setuptools config Without this, the module is not included in the distribution and users get an ImportError after pip install. * fix: correct indentation in on_trace_start properties dict * fix: prevent unbounded growth of span/trace tracking dicts Add max entry limit and eviction for _span_start_times and _trace_metadata dicts. If on_span_end or on_trace_end is never called (e.g., due to an SDK exception), these dicts could grow indefinitely in long-running processes. * fix: resolve distinct_id from trace metadata in on_span_end Previously on_span_end always called _get_distinct_id(None), which meant callable distinct_id resolvers never received the trace object for spans. Now the resolved distinct_id is stored at trace start and looked up by trace_id during span end. * refactor: extract _base_properties helper to reduce duplication All span handlers repeated the same 6 base fields (trace_id, span_id, parent_id, provider, framework, latency) plus the group_id conditional. Extract into a shared helper to reduce ~100 lines of boilerplate. * test: add missing edge case tests for openai agents processor - test_generation_span_with_no_usage: zero tokens when usage is None - test_generation_span_with_partial_usage: only input_tokens present - test_error_type_categorization_by_type_field_only: type field without matching message content - test_distinct_id_resolved_from_trace_for_spans: callable resolver uses trace context for span events - test_eviction_of_stale_entries: memory leak prevention works * fix: handle non-dict error_info in span error parsing If span.error is a string instead of a dict, calling .get() would raise AttributeError. Now falls back to str() for non-dict errors. * style: apply ruff formatting * style: replace lambda assignments with def (ruff E731) * fix: restore full CHANGELOG.md history The rebase conflict resolution accidentally truncated the changelog to only the most recent entries. Restored all historical entries. * fix: preserve personless mode for trace-id fallback distinct IDs When no distinct_id is provided, _get_distinct_id falls back to trace_id or "unknown". Since these are non-None strings, the $process_person_profile=False check in _capture_event never fired, creating unwanted person profiles keyed by trace IDs. Track whether the user explicitly provided a distinct_id and use that flag to control personless mode, matching the pattern used by the langchain and openai integrations. * fix: restore changelog history and fix personless mode edge cases Two fixes from bot review: 1. CHANGELOG.md was accidentally truncated to 38 lines during rebase conflict resolution. Restored all 767 lines of history. 2. Personless mode now follows the same pattern as langchain/openai integrations: _get_distinct_id returns None when no user-provided ID is available, and callers set $process_person_profile=False before falling back to trace_id. This covers the edge case where a callable distinct_id returns None. * fix: handle None token counts in generation span Guard against input_tokens or output_tokens being None when computing $ai_total_tokens to avoid TypeError. * fix: check error_type_raw for all error categories Check both error_type_raw and error_message for guardrail and max_turns errors, consistent with how ModelBehaviorError and UserError are already checked. * fix: add type hints to instrument() function * refactor: rename _safe_json to _ensure_serializable for clarity The function validates JSON serializability and falls back to str(), not serializes. Rename and update docstring to make the contract clear. * refactor: emit $ai_trace at trace end instead of start Move the $ai_trace event from on_trace_start to on_trace_end to capture full metadata including latency, matching the LangChain integration approach. on_trace_start now only stores metadata for use by spans. * style: fix ruff formatting * fix: add TYPE_CHECKING imports for type hints in instrument()
1 parent 661a0ec commit 1875b71

File tree

7 files changed

+1760
-1
lines changed

7 files changed

+1760
-1
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# 7.7.0 - 2026-01-15
2+
3+
feat(ai): Add OpenAI Agents SDK integration
4+
5+
Automatic tracing for agent workflows, handoffs, tool calls, guardrails, and custom spans. Includes `$ai_total_tokens`, `$ai_error_type` categorization, and `$ai_framework` property.
6+
17
# 7.6.0 - 2026-01-12
28

39
feat: add device_id to flags request payload
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Union
4+
5+
if TYPE_CHECKING:
6+
from agents.tracing import Trace
7+
8+
from posthog.client import Client
9+
10+
try:
11+
import agents # noqa: F401
12+
except ImportError:
13+
raise ModuleNotFoundError(
14+
"Please install the OpenAI Agents SDK to use this feature: 'pip install openai-agents'"
15+
)
16+
17+
from posthog.ai.openai_agents.processor import PostHogTracingProcessor
18+
19+
__all__ = ["PostHogTracingProcessor", "instrument"]
20+
21+
22+
def instrument(
23+
client: Optional[Client] = None,
24+
distinct_id: Optional[Union[str, Callable[[Trace], Optional[str]]]] = None,
25+
privacy_mode: bool = False,
26+
groups: Optional[Dict[str, Any]] = None,
27+
properties: Optional[Dict[str, Any]] = None,
28+
) -> PostHogTracingProcessor:
29+
"""
30+
One-liner to instrument OpenAI Agents SDK with PostHog tracing.
31+
32+
This registers a PostHogTracingProcessor with the OpenAI Agents SDK,
33+
automatically capturing traces, spans, and LLM generations.
34+
35+
Args:
36+
client: Optional PostHog client instance. If not provided, uses the default client.
37+
distinct_id: Optional distinct ID to associate with all traces.
38+
Can also be a callable that takes a trace and returns a distinct ID.
39+
privacy_mode: If True, redacts input/output content from events.
40+
groups: Optional PostHog groups to associate with events.
41+
properties: Optional additional properties to include with all events.
42+
43+
Returns:
44+
PostHogTracingProcessor: The registered processor instance.
45+
46+
Example:
47+
```python
48+
from posthog.ai.openai_agents import instrument
49+
50+
# Simple setup
51+
instrument(distinct_id="user@example.com")
52+
53+
# With custom properties
54+
instrument(
55+
distinct_id="user@example.com",
56+
privacy_mode=True,
57+
properties={"environment": "production"}
58+
)
59+
60+
# Now run agents as normal - traces automatically sent to PostHog
61+
from agents import Agent, Runner
62+
agent = Agent(name="Assistant", instructions="You are helpful.")
63+
result = Runner.run_sync(agent, "Hello!")
64+
```
65+
"""
66+
from agents.tracing import add_trace_processor
67+
68+
processor = PostHogTracingProcessor(
69+
client=client,
70+
distinct_id=distinct_id,
71+
privacy_mode=privacy_mode,
72+
groups=groups,
73+
properties=properties,
74+
)
75+
add_trace_processor(processor)
76+
return processor

0 commit comments

Comments
 (0)