13
13
from sentry .api .utils import get_date_range_from_stats_period
14
14
from sentry .exceptions import InvalidParams
15
15
from sentry .feedback .usecases .feedback_summaries import generate_summary
16
+ from sentry .grouping .utils import hash_from_values
16
17
from sentry .issues .grouptype import FeedbackGroup
17
18
from sentry .models .group import Group , GroupStatus
18
19
from sentry .models .organization import Organization
20
+ from sentry .utils .cache import cache
19
21
20
22
logger = logging .getLogger (__name__ )
21
23
24
26
# Token limit is 1,048,576 tokens, see https://ai.google.dev/gemini-api/docs/models#gemini-2.0-flash
25
27
MAX_FEEDBACKS_TO_SUMMARIZE_CHARS = 1000000
26
28
29
+ # One day since the cache key includes the start and end dates at hour granularity
30
+ SUMMARY_CACHE_TIMEOUT = 86400
31
+
27
32
28
33
@region_silo_endpoint
29
34
class OrganizationFeedbackSummaryEndpoint (OrganizationEndpoint ):
@@ -63,12 +68,30 @@ def get(self, request: Request, organization: Organization) -> Response:
63
68
except InvalidParams :
64
69
raise ParseError (detail = "Invalid or missing date range" )
65
70
71
+ projects = self .get_projects (request , organization )
72
+
73
+ # Sort first, then convert each element to a string
74
+ numeric_project_ids = sorted ([project .id for project in projects ])
75
+ project_ids = [str (project_id ) for project_id in numeric_project_ids ]
76
+ hashed_project_ids = hash_from_values (project_ids )
77
+
78
+ summary_cache_key = f"feedback_summary:{ organization .id } :{ start .strftime ('%Y-%m-%d-%H' )} :{ end .strftime ('%Y-%m-%d-%H' )} :{ hashed_project_ids } "
79
+ summary_cache = cache .get (summary_cache_key )
80
+ if summary_cache :
81
+ return Response (
82
+ {
83
+ "summary" : summary_cache ["summary" ],
84
+ "success" : True ,
85
+ "numFeedbacksUsed" : summary_cache ["numFeedbacksUsed" ],
86
+ }
87
+ )
88
+
66
89
filters = {
67
90
"type" : FeedbackGroup .type_id ,
68
91
"first_seen__gte" : start ,
69
92
"first_seen__lte" : end ,
70
93
"status" : GroupStatus .UNRESOLVED ,
71
- "project__in" : self . get_projects ( request , organization ) ,
94
+ "project__in" : projects ,
72
95
}
73
96
74
97
groups = Group .objects .filter (** filters ).order_by ("-first_seen" )[
@@ -77,7 +100,13 @@ def get(self, request: Request, organization: Organization) -> Response:
77
100
78
101
if groups .count () < MIN_FEEDBACKS_TO_SUMMARIZE :
79
102
logger .error ("Too few feedbacks to summarize" )
80
- return Response ({"summary" : None , "success" : False , "numFeedbacksUsed" : 0 })
103
+ return Response (
104
+ {
105
+ "summary" : None ,
106
+ "success" : False ,
107
+ "numFeedbacksUsed" : 0 ,
108
+ }
109
+ )
81
110
82
111
# Also cap the number of characters that we send to the LLM
83
112
group_feedbacks = []
@@ -99,6 +128,16 @@ def get(self, request: Request, organization: Organization) -> Response:
99
128
logger .exception ("Error generating summary of user feedbacks" )
100
129
return Response ({"detail" : "Error generating summary" }, status = 500 )
101
130
131
+ cache .set (
132
+ summary_cache_key ,
133
+ {"summary" : summary , "numFeedbacksUsed" : len (group_feedbacks )},
134
+ timeout = SUMMARY_CACHE_TIMEOUT ,
135
+ )
136
+
102
137
return Response (
103
- {"summary" : summary , "success" : True , "numFeedbacksUsed" : len (group_feedbacks )}
138
+ {
139
+ "summary" : summary ,
140
+ "success" : True ,
141
+ "numFeedbacksUsed" : len (group_feedbacks ),
142
+ }
104
143
)
0 commit comments