Skip to content
Merged
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 .changeset/openai-agents-group-id-session.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@posthog/ai': minor
---

The OpenAI Agents SDK `groupId` now also maps to `$ai_session_id` on `$ai_trace` and span events, so grouped runs show up as sessions in PostHog AI observability. `$ai_group_id` is still emitted alongside it.
5 changes: 5 additions & 0 deletions packages/ai/src/openai-agents/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ export class PostHogTracingProcessor implements TracingProcessor {
...errorProperties,
}
if (groupId) {
properties.$ai_session_id = groupId
properties.$ai_group_id = groupId
}
return properties
Expand Down Expand Up @@ -341,7 +342,11 @@ export class PostHogTracingProcessor implements TracingProcessor {
properties.$ai_latency = latency
}

// The Agents SDK groupId links traces from one conversation, which is exactly
// what PostHog calls a session. $ai_group_id is still emitted for anyone
// already querying it.
if (groupId) {
properties.$ai_session_id = groupId
properties.$ai_group_id = groupId
}

Expand Down
17 changes: 14 additions & 3 deletions packages/ai/tests/openai-agents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,24 @@ describe('PostHogTracingProcessor', () => {
expect(call.properties.$ai_latency).toBeDefined()
})

it('includes group_id in trace events', async () => {
it('includes group_id in trace and span events as both session and group id', async () => {
const trace = createMockTrace({ groupId: 'group_abc' })
const span = createMockSpan({ spanData: { type: 'generation', model: 'gpt-4o' } })

await processor.onTraceStart(trace as any)
mockClient.capture.mockClear()

await processor.onSpanStart(span as any)
await processor.onSpanEnd(span as any)
const spanCall = mockClient.capture.mock.calls[0][0]

await processor.onTraceEnd(trace as any)
const traceCall = mockClient.capture.mock.calls[1][0]

const call = mockClient.capture.mock.calls[0][0]
expect(call.properties.$ai_group_id).toBe('group_abc')
expect(spanCall.properties.$ai_session_id).toBe('group_abc')
expect(spanCall.properties.$ai_group_id).toBe('group_abc')
expect(traceCall.properties.$ai_session_id).toBe('group_abc')
expect(traceCall.properties.$ai_group_id).toBe('group_abc')
})

it('includes trace metadata in trace events', async () => {
Expand Down
Loading