feat(ai): add chat span lifecycle and step enrichment#1108
feat(ai): add chat span lifecycle and step enrichment#1108constantinius wants to merge 10 commits intoconstantinius/feat/ai/integration-basefrom
Conversation
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
There was a problem hiding this comment.
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
setConversationIdOnSpansto apply conversation IDs only to chat spans so the agent span is no longer transiently overwritten with stale data.
- Updated
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); | ||
| } | ||
| } |
There was a problem hiding this comment.
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)
| $events->listen(RequestSending::class, [$this, 'handleHttpRequestSending']); | ||
| $events->listen(ResponseReceived::class, [$this, 'handleHttpResponseReceived']); | ||
| $events->listen(ConnectionFailed::class, [$this, 'handleHttpConnectionFailed']); | ||
| } |
There was a problem hiding this comment.
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)
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.
b1ffab5 to
ac76add
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
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).
ac76add to
8b2f5b4
Compare
| $events->listen('Laravel\Ai\Events\StreamingAgent', [$this, 'handlePromptingAgentForTracing']); | ||
| $events->listen('Laravel\Ai\Events\AgentStreamed', [$this, 'handleAgentPromptedForTracing']); |
There was a problem hiding this comment.
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
8b2f5b4 to
4b1fa6a
Compare



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
RequestSendingResponseReceivedConnectionFailedgen_ai.chatspans under activeinvoke_agentspans.enrichChatSpansWithStepData:tracing.gen_ai_chattoggle.Made-with: Cursor
Issues