your_ai_insight 1.0.12 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d1204c7328e49de4459471b39022f9d2482ddebc0e72f26774cf889d1feb7fc
4
- data.tar.gz: c01acdcda667e9bf44ba364d5e9c48138da79ae42375c8a327c65a17c6d1b4c6
3
+ metadata.gz: e5d21f75a48c766bf8a0e2ec1eb1b6f8d7e72d90f156acd8c04f8cf2c693480a
4
+ data.tar.gz: 839c5a52f5406dbb9bfcf8e3e1ac6a682d2d1436fca48de0cf8f473d4863ed96
5
5
  SHA512:
6
- metadata.gz: 53ee1ee3ab937cd1ec3436f44af642a687e223b5c62ba05d7da3d43a0f8f2cc8fbba7c6e577bb9a240f31a19394b3ffa6d78880686d75f0c9d8d5f2c0adca541
7
- data.tar.gz: 401fba06aff405ab6d07c08aa547b3514ae365fd155add6b61b535d835e22b2254210ff2706e84a8b15abff2a15868274b7035574351f0a04fc801bea8776d1d
6
+ metadata.gz: f4bc7ce264f285eda38238b4f65cc009dd35475dfaab7c5dcf11a8ed310e81b5a598a73b7da473f17680283a4866c2c484d740ff333bc277112ae2529cf428d6
7
+ data.tar.gz: 52ff32ccb920df4dd04bee5d9a3f638a83b9c4ecae43c835719f6b61876fa007252a79042e689c5429f8efb3a1082e8cb9d11e383644ff0e12e5cc5b76391a95
@@ -4,6 +4,16 @@ module YourAiInsight
4
4
  class ClaudeService
5
5
  include HTTParty
6
6
 
7
+ # Ordered list of free models to try when the primary is rate-limited.
8
+ # OpenRouter routes to whichever provider has capacity.
9
+ FREE_FALLBACK_MODELS = [
10
+ "google/gemini-2.0-flash-exp:free",
11
+ "deepseek/deepseek-chat:free",
12
+ "qwen/qwen-2.5-72b-instruct:free",
13
+ "mistralai/mistral-7b-instruct:free",
14
+ "meta-llama/llama-3.3-70b-instruct:free"
15
+ ].freeze
16
+
7
17
  SYSTEM_PROMPT = <<~PROMPT.freeze
8
18
  You are an expert facility management analyst for AllPro IFM.
9
19
  You have deep knowledge of construction/facility jobs, subcontractor management,
@@ -35,10 +45,11 @@ module YourAiInsight
35
45
  @api_key = cfg.openrouter_api_key ||
36
46
  ENV["OPENROUTER_API_KEY"] ||
37
47
  raise(ConfigurationError, "Set config.openrouter_api_key or ENV['OPENROUTER_API_KEY']")
38
- @model = cfg.openrouter_model
39
- @max_tokens = cfg.max_tokens
40
- @site_url = cfg.openrouter_site_url
41
- @site_name = cfg.openrouter_site_name
48
+ @max_tokens = cfg.max_tokens
49
+ @site_url = cfg.openrouter_site_url
50
+ @site_name = cfg.openrouter_site_name
51
+ @primary_model = cfg.openrouter_model
52
+ @fallback_models = cfg.openrouter_fallback_models || FREE_FALLBACK_MODELS
42
53
  end
43
54
 
44
55
  # Free-form chat with optional live data context injected
@@ -47,18 +58,46 @@ module YourAiInsight
47
58
  role: "user",
48
59
  content: [context_block(context), user_message].reject(&:blank?).join("\n\n")
49
60
  }]
50
- call_api(messages)
61
+ call_api_with_fallback(messages)
51
62
  end
52
63
 
53
64
  # Structured report generation
54
65
  def generate_report(report_type, data)
55
66
  messages = [{ role: "user", content: report_prompt(report_type, data) }]
56
- call_api(messages)
67
+ call_api_with_fallback(messages)
57
68
  end
58
69
 
59
70
  private
60
71
 
61
- def call_api(messages)
72
+ # Try primary model first, then each fallback on 429 / provider error
73
+ def call_api_with_fallback(messages)
74
+ models_to_try = ([@primary_model] + @fallback_models).uniq.compact
75
+
76
+ last_error = nil
77
+ models_to_try.each do |model|
78
+ begin
79
+ result = call_api(messages, model)
80
+ return result
81
+ rescue ApiError => e
82
+ last_error = e
83
+ if rate_limited?(e)
84
+ Rails.logger.warn("[YourAiInsight] #{model} rate-limited, trying next model...")
85
+ next
86
+ else
87
+ raise
88
+ end
89
+ end
90
+ end
91
+
92
+ raise last_error || ApiError.new("All models exhausted with no response")
93
+ end
94
+
95
+ def rate_limited?(error)
96
+ msg = error.message.to_s.downcase
97
+ msg.include?("429") || msg.include?("rate limit") || msg.include?("provider returned error")
98
+ end
99
+
100
+ def call_api(messages, model)
62
101
  headers = {
63
102
  "Authorization" => "Bearer #{@api_key}",
64
103
  "Content-Type" => "application/json"
@@ -72,7 +111,7 @@ module YourAiInsight
72
111
  "https://openrouter.ai/api/v1/chat/completions",
73
112
  headers: headers,
74
113
  body: {
75
- model: @model,
114
+ model: model,
76
115
  max_tokens: @max_tokens,
77
116
  messages: full_messages
78
117
  }.to_json,
@@ -86,7 +125,7 @@ module YourAiInsight
86
125
  end
87
126
 
88
127
  body.dig("choices", 0, "message", "content").presence ||
89
- raise(ApiError, "Empty response from OpenRouter")
128
+ raise(ApiError, "Empty response from OpenRouter (model: #{model})")
90
129
 
91
130
  rescue HTTParty::Error, Timeout::Error => e
92
131
  raise ApiError, "Network error reaching OpenRouter: #{e.message}"
@@ -3,9 +3,11 @@ module YourAiInsight
3
3
  # ── OpenRouter (required) ──────────────────────────────────────────────────
4
4
  attr_accessor :openrouter_api_key
5
5
  # See https://openrouter.ai/models?q=free for free models
6
- attr_accessor :openrouter_model # default: meta-llama/llama-3.3-70b-instruct:free
7
- attr_accessor :openrouter_site_url # optional sent as HTTP-Referer
8
- attr_accessor :openrouter_site_name # optional — sent as X-Title
6
+ attr_accessor :openrouter_model # default: google/gemini-2.0-flash-exp:free
7
+ # Tried in order when the primary model returns 429 / provider error
8
+ attr_accessor :openrouter_fallback_models
9
+ attr_accessor :openrouter_site_url # optional — sent as HTTP-Referer
10
+ attr_accessor :openrouter_site_name # optional — sent as X-Title
9
11
 
10
12
  # ── Response tuning ────────────────────────────────────────────────────────
11
13
  attr_accessor :max_tokens # default: 2048
@@ -90,9 +92,10 @@ module YourAiInsight
90
92
  attr_accessor :sub_contractors_table # "sub_contractors"
91
93
 
92
94
  def initialize
93
- @openrouter_model = "meta-llama/llama-3.3-70b-instruct:free"
94
- @openrouter_site_url = nil
95
- @openrouter_site_name = nil
95
+ @openrouter_model = "google/gemini-2.0-flash-exp:free"
96
+ @openrouter_fallback_models = nil # uses ClaudeService::FREE_FALLBACK_MODELS when nil
97
+ @openrouter_site_url = nil
98
+ @openrouter_site_name = nil
96
99
  @max_tokens = 2048
97
100
  @company_name = "AllPro IFM"
98
101
  @logo_url = nil
@@ -1,3 +1,3 @@
1
1
  module YourAiInsight
2
- VERSION = "1.0.12"
2
+ VERSION = "1.0.13"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: your_ai_insight
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.12
4
+ version: 1.0.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - AllPro IFM