Skip to content

feat(ai): add chat span lifecycle and step enrichment#1108

Open
constantinius wants to merge 10 commits intoconstantinius/feat/ai/integration-basefrom
constantinius/feat/ai/chat-spans
Open

feat(ai): add chat span lifecycle and step enrichment#1108
constantinius wants to merge 10 commits intoconstantinius/feat/ai/integration-basefrom
constantinius/feat/ai/chat-spans

Conversation

@constantinius
Copy link
Copy Markdown

@constantinius constantinius commented Mar 16, 2026

Capture provider HTTP request/response events as gen_ai.chat spans under invoke_agent and enrich each chat span with response model, usage, finish reason, and conversation id mapping. This keeps tool/message/embeddings behavior out of scope for follow-up stacked branches.

Included

  • Adds HTTP event listeners for chat lifecycle:
    • RequestSending
    • ResponseReceived
    • ConnectionFailed
  • Starts/finishes gen_ai.chat spans under active invoke_agent spans.
  • Adds chat span enrichment via enrichChatSpansWithStepData:
    • response model
    • finish reason
    • usage (step-level / fallback where applicable)
  • Propagates conversation id to chat spans.
  • Adds tracing.gen_ai_chat toggle.

Made-with: Cursor

Issues

Add the initial AI integration wiring that records invoke_agent spans for prompting and prompted lifecycle events, including model/provider and token usage metadata. This establishes the stack base without chat, tool, message, or embeddings instrumentation.

Made-with: Cursor
@linear-code
Copy link
Copy Markdown

linear-code bot commented Mar 16, 2026

@constantinius constantinius requested a review from Litarnus March 16, 2026 14:35
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Agent span data transiently overwritten in setConversationIdOnSpans
    • Updated setConversationIdOnSpans to apply conversation IDs only to chat spans so the agent span is no longer transiently overwritten with stale data.

Create PR

Or push these changes by commenting:

@cursor push f4a1518091
Preview (f4a1518091)
diff --git a/src/Sentry/Laravel/Features/AiIntegration.php b/src/Sentry/Laravel/Features/AiIntegration.php
--- a/src/Sentry/Laravel/Features/AiIntegration.php
+++ b/src/Sentry/Laravel/Features/AiIntegration.php
@@ -290,13 +290,8 @@
     private function setConversationIdOnSpans(string $invocationId, string $conversationId): void
     {
         $invocation = $this->invocations[$invocationId];
-        $spans = [$invocation->span];
 
-        foreach ($invocation->chatSpans as $chatSpan) {
-            $spans[] = $chatSpan;
-        }
-
-        foreach ($spans as $span) {
+        foreach ($invocation->chatSpans as $span) {
             $data = $span->getData();
             $data['gen_ai.conversation.id'] = $conversationId;
             $span->setData($data);

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

$data['gen_ai.conversation.id'] = $conversationId;
$span->setData($data);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agent span data transiently overwritten in setConversationIdOnSpans

Low Severity

setConversationIdOnSpans includes $invocation->span (the agent span) in its loop at line 293, but the caller handleAgentPromptedForTracing already handles the agent span separately — line 143 adds the conversation ID to the local $data, and line 147 overwrites the agent span via $agentSpan->setData($data). Inside setConversationIdOnSpans, calling $span->getData() on the agent span retrieves the original data without the enrichment added at lines 126–139 (response model, provider, usage), then writes it back. This transiently strips the agent span of not-yet-applied enrichments and is immediately overwritten anyway. Only the chat span modifications are meaningful here.

Additional Locations (1)
Fix in Cursor Fix in Web

@constantinius constantinius changed the base branch from constantinius/feat/ai/integration to constantinius/feat/ai/integration-base March 16, 2026 14:53
$events->listen(RequestSending::class, [$this, 'handleHttpRequestSending']);
$events->listen(ResponseReceived::class, [$this, 'handleHttpResponseReceived']);
$events->listen(ConnectionFailed::class, [$this, 'handleHttpConnectionFailed']);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate spans for AI HTTP requests in default config

Medium Severity

AiIntegration registers listeners for RequestSending, ResponseReceived, and ConnectionFailed — the same events that HttpClientIntegration already listens to. Both features default to enabled (http_client_requests and gen_ai_chat are both true), so every AI provider HTTP request produces a redundant http.client span alongside the gen_ai.chat span as siblings under the agent span. The test file confirms awareness of this conflict by setting 'sentry.tracing.http_client_requests' => false in $defaultSetupConfig, but production users get duplicate spans by default with no automatic suppression.

Additional Locations (1)
Fix in Cursor Fix in Web

Replace inline stub definitions for Laravel AI interfaces, classes,
events, attributes, and file types with the real implementations from
the laravel/ai package added as a dev dependency.

Drop PHP version and Laravel version skip guards from setUp() since
these tests only run on PHP 8.4+.

Simplify TestAgentWithConfig to use native PHP 8 attributes directly
instead of the eval() workaround for older PHP versions.
@constantinius constantinius force-pushed the constantinius/feat/ai/chat-spans branch 2 times, most recently from b1ffab5 to ac76add Compare April 2, 2026 13:11
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Remove laravel/ai globally in both phpunit and phpunit-legacy jobs
since it requires PHP 8.4+ and only supports Laravel 12. Re-add it
conditionally for the supported combination (PHP 8.4+, Laravel 12).
@constantinius constantinius force-pushed the constantinius/feat/ai/chat-spans branch from ac76add to 8b2f5b4 Compare April 2, 2026 13:31
Comment on lines +35 to +36
$events->listen('Laravel\Ai\Events\StreamingAgent', [$this, 'handlePromptingAgentForTracing']);
$events->listen('Laravel\Ai\Events\AgentStreamed', [$this, 'handleAgentPromptedForTracing']);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The AgentStreamed event is dispatched to the handleAgentPromptedForTracing handler, which strictly type-hints AgentPrompted, causing a TypeError.
Severity: CRITICAL

Suggested Fix

Create a separate handler method for the AgentStreamed event that correctly type-hints \Laravel\Ai\Events\AgentStreamed. Alternatively, if the logic is identical, use a common interface or base class for both event types and type-hint that instead. The same fix should be applied to the StreamingAgent and handlePromptingAgentForTracing listener.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/Sentry/Laravel/Features/AiIntegration.php#L35-L36

Potential issue: The event listener at line 36 registers the
`handleAgentPromptedForTracing` method to handle the `Laravel\Ai\Events\AgentStreamed`
event. However, the handler method at line 110 has a strict type declaration for
`\Laravel\Ai\Events\AgentPrompted`. Since `AgentStreamed` and `AgentPrompted` are
distinct classes, this mismatch will cause a fatal `TypeError` in PHP 8+ whenever a
streaming AI event is dispatched, leading to a crash. A similar issue exists for the
`StreamingAgent` event being routed to the `handlePromptingAgentForTracing` handler,
which type-hints `PromptingAgent`.

The Laravel AI classes are not available when PHPStan runs on PHP 8.3
without laravel/ai installed. Add baseline ignores matching the
existing pattern used for other optional packages (Sanctum, Livewire,
Lumen).
Capture provider HTTP request/response events as gen_ai.chat spans under invoke_agent and enrich each chat span with response model, usage, finish reason, and conversation id mapping. This keeps tool/message/embeddings behavior out of scope for follow-up stacked branches.

Made-with: Cursor
@constantinius constantinius force-pushed the constantinius/feat/ai/chat-spans branch from 8b2f5b4 to 4b1fa6a Compare April 2, 2026 13:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant