feat: move caching logic to label_suggestion

This commit is contained in:
Shivam Mishra
2025-12-17 17:42:51 +05:30
parent 89c7feeda7
commit bf7943fcc5
2 changed files with 38 additions and 54 deletions
+1 -49
View File
@@ -7,17 +7,11 @@ class Captain::BaseEditorService
# 120000 * 4 = 480,000 characters (rounding off downwards to 400,000 to be safe)
TOKEN_LIMIT = 400_000
GPT_MODEL = Llm::Config::DEFAULT_MODEL
CACHEABLE_EVENTS = [].freeze
pattr_initialize [:account!, :event!]
def perform
return value_from_cache if value_from_cache.present?
response = send("#{event_name}_message")
save_to_cache(response) if response.present?
response
send("#{event_name}_message")
end
private
@@ -26,52 +20,10 @@ class Captain::BaseEditorService
event['name']
end
def cache_key
return nil unless event_is_cacheable?
return nil unless conversation
# since the value from cache depends on the conversation last_activity_at, it will always be fresh
format(::Redis::Alfred::OPENAI_CONVERSATION_KEY, event_name: event_name, conversation_id: conversation.id,
updated_at: conversation.last_activity_at.to_i)
end
def value_from_cache
return nil unless event_is_cacheable?
return nil if cache_key.blank?
deserialize_cached_value(Redis::Alfred.get(cache_key))
end
def deserialize_cached_value(value)
return nil if value.blank?
JSON.parse(value, symbolize_names: true)
rescue JSON::ParserError
# If json parse failed, returning the value as is will fail too
# since we access the keys as symbols down the line
# So it's best to return nil
nil
end
def save_to_cache(response)
return nil unless event_is_cacheable?
# Serialize to JSON
# This makes parsing easy when response is a hash
Redis::Alfred.setex(cache_key, response.to_json)
end
def conversation
@conversation ||= account.conversations.find_by(display_id: event['data']['conversation_display_id'])
end
def event_is_cacheable?
# self.class::CACHEABLE_EVENTS is way to access CACHEABLE_EVENTS defined in the class hierarchy of the current object.
# This ensures that if CACHEABLE_EVENTS is updated elsewhere in it's ancestors, we access the latest value.
self.class::CACHEABLE_EVENTS.include?(event_name)
end
def api_base
endpoint = InstallationConfig.find_by(name: 'CAPTAIN_OPEN_AI_ENDPOINT')&.value.presence || 'https://api.openai.com/'
endpoint = endpoint.chomp('/')
+37 -5
View File
@@ -1,10 +1,14 @@
class Captain::LabelSuggestionService < Captain::BaseEditorService
CACHEABLE_EVENTS = %w[label_suggestion].freeze
def label_suggestion_message
# Check cache first
cached_response = read_from_cache
return cached_response if cached_response.present?
# Build content
content = labels_with_messages
return nil if content.blank?
# Make API call
response = make_api_call(
model: GPT_MODEL, # TODO: Use separate model for label suggestion
messages: [
@@ -14,13 +18,41 @@ class Captain::LabelSuggestionService < Captain::BaseEditorService
)
return response if response[:error].present?
# LLMs are not deterministic - sometimes response includes "Labels:" prefix
# TODO: Fix with better prompt
{ message: response[:message] ? response[:message].gsub(/^(label|labels):/i, '') : '' }
# Clean up response
result = { message: response[:message] ? response[:message].gsub(/^(label|labels):/i, '') : '' }
# Cache successful result
write_to_cache(result)
result
end
private
def cache_key
return nil unless conversation
format(
::Redis::Alfred::OPENAI_CONVERSATION_KEY,
event_name: 'label_suggestion',
conversation_id: conversation.id,
updated_at: conversation.last_activity_at.to_i
)
end
def read_from_cache
return nil unless cache_key
cached = Redis::Alfred.get(cache_key)
JSON.parse(cached, symbolize_names: true) if cached.present?
rescue JSON::ParserError
nil
end
def write_to_cache(response)
Redis::Alfred.setex(cache_key, response.to_json) if cache_key
end
def labels_with_messages
return nil unless valid_conversation?(conversation)