Module: Observ::PromptManager::Caching

Included in:
Observ::PromptManager
Defined in:
app/services/observ/prompt_manager/caching.rb

Overview

Concern for prompt caching operations including cache key generation, fetching with cache, invalidation, and cache warming.

Instance Method Summary collapse

Instance Method Details

#bump_cache_stamp(name:) ⇒ Object



133
134
135
# File 'app/services/observ/prompt_manager/caching.rb', line 133

def bump_cache_stamp(name:)
  Rails.cache.write(cache_stamp_key(name: name), Time.current.to_f)
end

#cache_key(name:, state: nil, version: nil) ⇒ String

Enhanced cache key strategy

Parameters:

  • name (String)

    The prompt name

  • state (Symbol, nil) (defaults to: nil)

    The prompt state (:draft, :production, :archived)

  • version (Integer, nil) (defaults to: nil)

    The prompt version number

Returns:

  • (String)

    The cache key for the prompt



17
18
19
20
21
22
23
24
25
26
27
# File 'app/services/observ/prompt_manager/caching.rb', line 17

def cache_key(name:, state: nil, version: nil)
  namespace = Observ.config.prompt_cache_namespace

  if version
    "#{namespace}:#{name}:version:#{version}"
  elsif state
    "#{namespace}:#{name}:state:#{state}"
  else
    "#{namespace}:#{name}:production"
  end
end

#cache_stamp(name:) ⇒ Object



129
130
131
# File 'app/services/observ/prompt_manager/caching.rb', line 129

def cache_stamp(name:)
  Rails.cache.read(cache_stamp_key(name: name))
end

#cache_stamp_key(name:) ⇒ Object



125
126
127
# File 'app/services/observ/prompt_manager/caching.rb', line 125

def cache_stamp_key(name:)
  "#{Observ.config.prompt_cache_namespace}:#{name}:stamp"
end

#critical_prompt_namesArray<String>

Get list of critical prompts (prompts used by agents)

Returns:

  • (Array<String>)

    Array of prompt names



139
140
141
142
143
144
# File 'app/services/observ/prompt_manager/caching.rb', line 139

def critical_prompt_names
  return Observ.config.prompt_cache_critical_prompts if Observ.config.prompt_cache_critical_prompts.any?

  # Auto-discover from production prompts
  Observ::Prompt.where(state: :production).distinct.pluck(:name)
end

#fetch(name:, state: :production, version: nil, fallback: nil) ⇒ Observ::Prompt, Observ::NullPrompt

Fetch single prompt with advanced caching

Parameters:

  • name (String)

    The prompt name

  • state (Symbol) (defaults to: :production)

    The prompt state (default: :production)

  • version (Integer, nil) (defaults to: nil)

    Specific version to fetch

  • fallback (String, nil) (defaults to: nil)

    Fallback text if prompt not found

Returns:



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'app/services/observ/prompt_manager/caching.rb', line 39

def fetch(name:, state: :production, version: nil, fallback: nil)
  return fetch_from_db(name: name, state: state, version: version, fallback: fallback) unless caching_enabled?

  cache_key_value = cache_key(name: name, state: state, version: version)

  # Check if value exists in cache
  cache_hit = Rails.cache.exist?(cache_key_value)

  result = Rails.cache.fetch(cache_key_value, expires_in: Observ.config.prompt_cache_ttl) do
    fetch_from_db(name: name, state: state, version: version, fallback: fallback).tap do |prompt|
      # Only track cache miss for real prompts (not NullPrompt)
      if Observ.config.prompt_cache_monitoring_enabled && prompt && !prompt.is_a?(NullPrompt)
        track_cache_miss(name, state, version)
      end
    end
  end

  # Only track hit if it was actually in cache and is a real prompt
  if cache_hit && result && !result.is_a?(NullPrompt) && Observ.config.prompt_cache_monitoring_enabled
    track_cache_hit(name, state, version)
  end

  result
rescue => e
  Rails.logger.error("Cache fetch failed for #{name}: #{e.message}")
  fetch_from_db(name: name, state: state, version: version, fallback: fallback)
end

#fetch_all(names:, state: :production) ⇒ Hash

Fetch multiple prompts at once

Parameters:

  • names (Array<String>)

    The prompt names to fetch

  • state (Symbol) (defaults to: :production)

    The prompt state (default: :production)

Returns:

  • (Hash)

    Hash of prompt names to prompt objects



71
72
73
# File 'app/services/observ/prompt_manager/caching.rb', line 71

def fetch_all(names:, state: :production)
  Prompt.where(name: names, state: state).index_by(&:name)
end

#invalidate_cache(name:, version: nil) ⇒ Boolean

Invalidate cache for a prompt

Parameters:

  • name (String)

    The prompt name

  • version (Integer, nil) (defaults to: nil)

    Specific version to invalidate (nil = all states)

Returns:

  • (Boolean)

    true if successful



83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'app/services/observ/prompt_manager/caching.rb', line 83

def invalidate_cache(name:, version: nil)
  keys = if version
    [cache_key(name: name, version: version)]
  else
    # Invalidate all state-based keys for this prompt
    [:draft, :production, :archived].map { |state| cache_key(name: name, state: state) }
  end

  keys.each { |key| Rails.cache.delete(key) }
  bump_cache_stamp(name: name)
  Rails.logger.info("Cache invalidated for #{name}#{version ? " v#{version}" : ""}")

  true
end

#warm_cache(prompt_names = nil) ⇒ Hash

Warm cache for critical prompts

Parameters:

  • prompt_names (Array<String>, nil) (defaults to: nil)

    Specific prompts to warm (nil = all critical)

Returns:

  • (Hash)

    Hash with :success and :failed arrays



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'app/services/observ/prompt_manager/caching.rb', line 105

def warm_cache(prompt_names = nil)
  names = prompt_names || critical_prompt_names

  results = { success: [], failed: [] }

  names.each do |name|
    begin
      # Fetch production version to warm cache
      fetch(name: name, state: :production)
      results[:success] << name
    rescue => e
      results[:failed] << { name: name, error: e.message }
      Rails.logger.error("Failed to warm cache for #{name}: #{e.message}")
    end
  end

  Rails.logger.info("Cache warming completed: #{results[:success].count} success, #{results[:failed].count} failed")
  results
end