Skip to content

Commit e2ca39e

Browse files
authored
feat(llm-detector): Send IssueOccurrence for LLM Detected issues (#102452)
now that we have a request to the Seer endpoint, we can use the output to send an IssueOccurrence. Right now we are just sending over all the data we have from the response + a basic fingerprint, but we expect these to change as we iterate on the data we want to show in the issue and the actual request/response from the LLM. this is just a basic version that allows us to test this end to end. https://linear.app/getsentry/issue/ID-1012/create-issues-with-llm-output
1 parent c4382bb commit e2ca39e

File tree

2 files changed

+287
-185
lines changed

2 files changed

+287
-185
lines changed

src/sentry/tasks/llm_issue_detection.py

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
from __future__ import annotations
22

33
import logging
4+
import random
5+
from datetime import UTC, datetime
6+
from uuid import uuid4
47

58
import sentry_sdk
69
from django.conf import settings
710
from pydantic import BaseModel
811

912
from sentry import options
13+
from sentry.issues.grouptype import LLMDetectedExperimentalGroupType
14+
from sentry.issues.issue_occurrence import IssueEvidence, IssueOccurrence
15+
from sentry.issues.producer import PayloadType, produce_occurrence_to_kafka
1016
from sentry.models.project import Project
1117
from sentry.net.http import connection_from_url
1218
from sentry.seer.explorer.index_data import get_trace_for_transaction, get_transactions_for_project
@@ -23,6 +29,8 @@
2329
SEER_TIMEOUT_S = 120
2430
SEER_RETRIES = 1
2531

32+
NUM_TRANSACTIONS_TO_PROCESS = 10
33+
2634

2735
seer_issue_detection_connection_pool = connection_from_url(
2836
settings.SEER_DEFAULT_URL,
@@ -61,6 +69,79 @@ def __init__(
6169
self.error_message = error_message
6270

6371

72+
def create_issue_occurrence_from_detection(
73+
detected_issue: DetectedIssue,
74+
trace: TraceData,
75+
project_id: int,
76+
transaction_name: str,
77+
) -> None:
78+
"""
79+
Create and produce an IssueOccurrence from an LLM-detected issue.
80+
"""
81+
event_id = uuid4().hex
82+
occurrence_id = uuid4().hex
83+
detection_time = datetime.now(UTC)
84+
85+
fingerprint = [f"llm-detected-{detected_issue.title}-{transaction_name}"]
86+
87+
evidence_data = {
88+
"trace_id": trace.trace_id,
89+
"transaction": transaction_name,
90+
"explanation": detected_issue.explanation,
91+
"impact": detected_issue.impact,
92+
}
93+
94+
evidence_display = [
95+
IssueEvidence(name="Explanation", value=detected_issue.explanation, important=True),
96+
IssueEvidence(name="Impact", value=detected_issue.impact, important=False),
97+
IssueEvidence(name="Evidence", value=detected_issue.evidence, important=False),
98+
]
99+
100+
if detected_issue.missing_telemetry:
101+
evidence_display.append(
102+
IssueEvidence(
103+
name="Missing Telemetry",
104+
value=detected_issue.missing_telemetry,
105+
important=False,
106+
)
107+
)
108+
109+
occurrence = IssueOccurrence(
110+
id=occurrence_id,
111+
event_id=event_id,
112+
project_id=project_id,
113+
fingerprint=fingerprint,
114+
issue_title=detected_issue.title,
115+
subtitle=detected_issue.explanation[:200], # Truncate for subtitle
116+
resource_id=None,
117+
evidence_data=evidence_data,
118+
evidence_display=evidence_display,
119+
type=LLMDetectedExperimentalGroupType,
120+
detection_time=detection_time,
121+
culprit=transaction_name,
122+
level="warning",
123+
)
124+
125+
event_data = {
126+
"event_id": event_id,
127+
"project_id": project_id,
128+
"platform": "other",
129+
"received": detection_time.isoformat(),
130+
"timestamp": detection_time.isoformat(),
131+
"tags": {
132+
"trace_id": trace.trace_id,
133+
"transaction": transaction_name,
134+
"llm_detected": "true",
135+
},
136+
}
137+
138+
produce_occurrence_to_kafka(
139+
payload_type=PayloadType.OCCURRENCE,
140+
occurrence=occurrence,
141+
event_data=event_data,
142+
)
143+
144+
64145
def get_enabled_project_ids() -> list[int]:
65146
"""
66147
Get the list of project IDs that are explicitly enabled for LLM detection.
@@ -94,7 +175,7 @@ def run_llm_issue_detection() -> None:
94175
@instrumented_task(
95176
name="sentry.tasks.llm_issue_detection.detect_llm_issues_for_project",
96177
namespace=issues_tasks,
97-
processing_deadline_duration=120,
178+
processing_deadline_duration=300,
98179
)
99180
def detect_llm_issues_for_project(project_id: int) -> None:
100181
"""
@@ -106,6 +187,11 @@ def detect_llm_issues_for_project(project_id: int) -> None:
106187
transactions = get_transactions_for_project(
107188
project_id, limit=50, start_time_delta={"minutes": 30}
108189
)
190+
if not transactions:
191+
return
192+
193+
# Sample a random subset of transactions to process
194+
transactions = random.sample(transactions, min(len(transactions), NUM_TRANSACTIONS_TO_PROCESS))
109195
for transaction in transactions:
110196
try:
111197
trace: TraceData | None = get_trace_for_transaction(
@@ -169,6 +255,17 @@ def detect_llm_issues_for_project(project_id: int) -> None:
169255
),
170256
},
171257
)
258+
for detected_issue in response_data.issues:
259+
try:
260+
create_issue_occurrence_from_detection(
261+
detected_issue=detected_issue,
262+
trace=trace,
263+
project_id=project_id,
264+
transaction_name=transaction.name,
265+
)
266+
267+
except Exception as e:
268+
sentry_sdk.capture_exception(e)
172269
except LLMIssueDetectionError as e:
173270
sentry_sdk.capture_exception(e)
174-
continue
271+
continue # if one transaction encounters an error, don't block processing of the others

0 commit comments

Comments
 (0)