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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e5d21f75a48c766bf8a0e2ec1eb1b6f8d7e72d90f156acd8c04f8cf2c693480a
|
|
4
|
+
data.tar.gz: 839c5a52f5406dbb9bfcf8e3e1ac6a682d2d1436fca48de0cf8f473d4863ed96
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
@
|
|
39
|
-
@
|
|
40
|
-
@
|
|
41
|
-
@
|
|
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
|
-
|
|
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
|
-
|
|
67
|
+
call_api_with_fallback(messages)
|
|
57
68
|
end
|
|
58
69
|
|
|
59
70
|
private
|
|
60
71
|
|
|
61
|
-
|
|
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:
|
|
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
|
|
7
|
-
|
|
8
|
-
attr_accessor :
|
|
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
|
|
94
|
-
@
|
|
95
|
-
@
|
|
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
|