your_ai_insight 1.0.10 → 1.0.11

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: 3241c83b9d80f1cffabef64df8128eb4d765b1c31667e256815257040bfb8861
4
- data.tar.gz: 0ebe08ba0157fa78175d2f1188f57d62d97fee0c384fcf627bd20d897eb4e460
3
+ metadata.gz: c400f6bb1a051f84900f4ca7c4d229e6f7193aff9bf27b1c6e13772b36935dc6
4
+ data.tar.gz: a05ff0274c36660ed5a0e0dfde6ec242e8c293384dc835fd1b02ba0c471e10bd
5
5
  SHA512:
6
- metadata.gz: 17e3e5509660d296e10855c05f3ac4df938ae9fc64974dc538c023d4b7e85d9d1ba994b7b205e7d157e88736559e6b6ef57e8b1f2fd66ef4580aba1d78fecada
7
- data.tar.gz: 94b4d799978ea42d41354b7d57089972243866ce4e2d2fb0a059dafb1f33159c657fe033d61cdbdbc31588781598ff1c033befa0f91717c4bc45dbf1df8e2c96
6
+ metadata.gz: e0893b4723fc2827ea30eabc856f9a48820bc377a286b017be3d78e486ffa218e5930c6f4dfb1fca77fc5616e87038912bd11fee75304638c25b6ebdb9a9d879
7
+ data.tar.gz: cc045b166a9f36f017d1f2a90f02412eb213c3d54ee0a739a7958e9a9dd47ec87779a25ac2362c9c460d99178a14cba294a84084efbccd1cc7cd6505f9490dd6
@@ -3,7 +3,6 @@ require "httparty"
3
3
  module YourAiInsight
4
4
  class ClaudeService
5
5
  include HTTParty
6
- base_uri "https://api.anthropic.com"
7
6
 
8
7
  SYSTEM_PROMPT = <<~PROMPT.freeze
9
8
  You are an expert facility management analyst for AllPro IFM.
@@ -32,11 +31,24 @@ module YourAiInsight
32
31
  PROMPT
33
32
 
34
33
  def initialize
35
- @api_key = YourAiInsight.config.anthropic_api_key ||
36
- ENV["ANTHROPIC_API_KEY"] ||
37
- raise(ConfigurationError, "Set YourAiInsight.configure { |c| c.anthropic_api_key = ENV['ANTHROPIC_API_KEY'] }")
38
- @model = YourAiInsight.config.claude_model
39
- @max_tokens = YourAiInsight.config.max_tokens
34
+ cfg = YourAiInsight.config
35
+ @provider = (cfg.ai_provider || :claude).to_sym
36
+ @max_tokens = cfg.max_tokens
37
+
38
+ case @provider
39
+ when :openrouter
40
+ @api_key = cfg.openrouter_api_key ||
41
+ ENV["OPENROUTER_API_KEY"] ||
42
+ raise(ConfigurationError, "Set config.openrouter_api_key or ENV['OPENROUTER_API_KEY']")
43
+ @model = cfg.openrouter_model
44
+ @site_url = cfg.openrouter_site_url
45
+ @site_name = cfg.openrouter_site_name
46
+ else
47
+ @api_key = cfg.anthropic_api_key ||
48
+ ENV["ANTHROPIC_API_KEY"] ||
49
+ raise(ConfigurationError, "Set config.anthropic_api_key or ENV['ANTHROPIC_API_KEY']")
50
+ @model = cfg.claude_model
51
+ end
40
52
  end
41
53
 
42
54
  # Free-form chat with optional live data context injected
@@ -57,8 +69,12 @@ module YourAiInsight
57
69
  private
58
70
 
59
71
  def call_api(messages)
72
+ @provider == :openrouter ? call_openrouter(messages) : call_claude(messages)
73
+ end
74
+
75
+ def call_claude(messages)
60
76
  response = self.class.post(
61
- "/v1/messages",
77
+ "https://api.anthropic.com/v1/messages",
62
78
  headers: {
63
79
  "x-api-key" => @api_key,
64
80
  "anthropic-version" => "2023-06-01",
@@ -74,7 +90,6 @@ module YourAiInsight
74
90
  )
75
91
 
76
92
  body = JSON.parse(response.body)
77
-
78
93
  unless response.success?
79
94
  raise ApiError, "Claude API error (HTTP #{response.code}): #{body.dig('error', 'message')}"
80
95
  end
@@ -86,6 +101,40 @@ module YourAiInsight
86
101
  raise ApiError, "Network error reaching Claude API: #{e.message}"
87
102
  end
88
103
 
104
+ def call_openrouter(messages)
105
+ headers = {
106
+ "Authorization" => "Bearer #{@api_key}",
107
+ "Content-Type" => "application/json"
108
+ }
109
+ headers["HTTP-Referer"] = @site_url if @site_url.present?
110
+ headers["X-Title"] = @site_name if @site_name.present?
111
+
112
+ # Prepend system prompt as a system message (OpenAI-compatible format)
113
+ full_messages = [{ role: "system", content: SYSTEM_PROMPT }] + messages
114
+
115
+ response = self.class.post(
116
+ "https://openrouter.ai/api/v1/chat/completions",
117
+ headers: headers,
118
+ body: {
119
+ model: @model,
120
+ max_tokens: @max_tokens,
121
+ messages: full_messages
122
+ }.to_json,
123
+ timeout: 90
124
+ )
125
+
126
+ body = JSON.parse(response.body)
127
+ unless response.success?
128
+ raise ApiError, "OpenRouter API error (HTTP #{response.code}): #{body.dig('error', 'message')}"
129
+ end
130
+
131
+ body.dig("choices", 0, "message", "content").presence ||
132
+ raise(ApiError, "Empty response from OpenRouter API")
133
+
134
+ rescue HTTParty::Error, Timeout::Error => e
135
+ raise ApiError, "Network error reaching OpenRouter API: #{e.message}"
136
+ end
137
+
89
138
  # Injects a live snapshot into the chat prompt
90
139
  def context_block(ctx)
91
140
  return nil if ctx.blank?
@@ -1,12 +1,21 @@
1
1
  module YourAiInsight
2
2
  class Configuration
3
- # ── Required ──────────────────────────────────────────────────────────────
4
- attr_accessor :anthropic_api_key
3
+ # ── AI Provider ───────────────────────────────────────────────────────────
4
+ # :claude (default) or :openrouter
5
+ attr_accessor :ai_provider
5
6
 
6
- # ── Model / Claude settings ───────────────────────────────────────────────
7
- attr_accessor :claude_model # default: claude-sonnet-4-20250514
7
+ # ── Claude / Anthropic settings ───────────────────────────────────────────
8
+ attr_accessor :anthropic_api_key
9
+ attr_accessor :claude_model # default: claude-opus-4-5
8
10
  attr_accessor :max_tokens # default: 2048
9
11
 
12
+ # ── OpenRouter settings ───────────────────────────────────────────────────
13
+ attr_accessor :openrouter_api_key
14
+ # default: meta-llama/llama-3.3-70b-instruct:free (free, no billing needed)
15
+ attr_accessor :openrouter_model
16
+ attr_accessor :openrouter_site_url # optional — sent as HTTP-Referer
17
+ attr_accessor :openrouter_site_name # optional — sent as X-Title
18
+
10
19
  # ── Multi-tenancy: scope data to current user's locations/customers ───────
11
20
  # Proc receives current_user and returns an Array of location_ids, or nil for all.
12
21
  # Example: config.location_scope = ->(user) { user.locations.pluck(:id) }
@@ -87,9 +96,14 @@ module YourAiInsight
87
96
  attr_accessor :sub_contractors_table # "sub_contractors"
88
97
 
89
98
  def initialize
90
- @claude_model = "claude-sonnet-4-20250514"
99
+ @ai_provider = :claude
100
+ @claude_model = "claude-opus-4-5"
91
101
  @max_tokens = 2048
92
102
  @company_name = "AllPro IFM"
103
+
104
+ @openrouter_model = "meta-llama/llama-3.3-70b-instruct:free"
105
+ @openrouter_site_url = nil
106
+ @openrouter_site_name = nil
93
107
  @logo_url = nil
94
108
  @default_recipients = []
95
109
 
@@ -1,3 +1,3 @@
1
1
  module YourAiInsight
2
- VERSION = "1.0.10"
2
+ VERSION = "1.0.11"
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.10
4
+ version: 1.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - AllPro IFM