agentbill-sdk 5.0.1 → 9.4.0
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 +4 -4
- data/.github/workflows/ci.yml +5 -4
- data/CHANGELOG.md +53 -1
- data/examples/ollama_basic.rb +81 -0
- data/examples/perplexity_basic.rb +66 -0
- data/lib/agentbill/agents.rb +226 -0
- data/lib/agentbill/attributes.rb +95 -0
- data/lib/agentbill/customers.rb +164 -0
- data/lib/agentbill/distributed.rb +109 -0
- data/lib/agentbill/exceptions.rb +84 -0
- data/lib/agentbill/ollama_wrapper.rb +133 -0
- data/lib/agentbill/orders.rb +283 -0
- data/lib/agentbill/perplexity_wrapper.rb +75 -0
- data/lib/agentbill/signal_types.rb +179 -0
- data/lib/agentbill/signals.rb +271 -0
- data/lib/agentbill/tracer.rb +68 -11
- data/lib/agentbill/tracing.rb +343 -0
- data/lib/agentbill/version.rb +1 -1
- data/lib/agentbill/wrappers.rb +384 -0
- data/lib/agentbill.rb +269 -82
- metadata +16 -2
data/lib/agentbill.rb
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
require 'net/http'
|
|
2
2
|
require 'json'
|
|
3
3
|
require 'securerandom'
|
|
4
|
+
require 'digest'
|
|
4
5
|
require_relative 'agentbill/version'
|
|
5
6
|
require_relative 'agentbill/tracer'
|
|
6
7
|
|
|
8
|
+
require_relative 'agentbill/customers'
|
|
9
|
+
require_relative 'agentbill/agents'
|
|
10
|
+
require_relative 'agentbill/orders'
|
|
11
|
+
|
|
7
12
|
module AgentBill
|
|
8
13
|
class Client
|
|
9
|
-
attr_reader :config, :tracer
|
|
14
|
+
attr_reader :config, :tracer, :customers, :agents, :orders
|
|
10
15
|
|
|
11
16
|
def initialize(config)
|
|
12
17
|
@config = config
|
|
13
18
|
@tracer = Tracer.new(config)
|
|
19
|
+
@customers = CustomersResource.new(config)
|
|
20
|
+
@agents = AgentsResource.new(config)
|
|
21
|
+
@orders = OrdersResource.new(config)
|
|
14
22
|
end
|
|
15
23
|
|
|
16
24
|
def self.init(config)
|
|
@@ -24,33 +32,21 @@ module AgentBill
|
|
|
24
32
|
[1, text.to_s.length / 4].max
|
|
25
33
|
end
|
|
26
34
|
|
|
27
|
-
|
|
28
|
-
# Model pricing (per 1K tokens)
|
|
29
|
-
pricing = {
|
|
30
|
-
'gpt-4' => { input: 0.03, output: 0.06 },
|
|
31
|
-
'gpt-4o' => { input: 0.005, output: 0.015 },
|
|
32
|
-
'gpt-4o-mini' => { input: 0.00015, output: 0.0006 },
|
|
33
|
-
'claude-sonnet-4-5' => { input: 0.003, output: 0.015 },
|
|
34
|
-
'claude-opus-4-1-20250805' => { input: 0.015, output: 0.075 },
|
|
35
|
-
'claude-3-5-sonnet-20241022' => { input: 0.003, output: 0.015 },
|
|
36
|
-
'mistral-large-latest' => { input: 0.004, output: 0.012 },
|
|
37
|
-
'gemini-pro' => { input: 0.00025, output: 0.0005 }
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
model_price = pricing[model] || { input: 0.001, output: 0.002 }
|
|
41
|
-
(input_tokens / 1000.0 * model_price[:input]) + (output_tokens / 1000.0 * model_price[:output])
|
|
42
|
-
end
|
|
35
|
+
# v9.1.0: estimate_cost() REMOVED. Cost is 100% server-side via model_pricing table.
|
|
43
36
|
|
|
44
37
|
def validate_request(model, messages, estimated_output_tokens = 1000)
|
|
45
|
-
|
|
38
|
+
# Always validate when customer_id is present - backend will check DB policies
|
|
39
|
+
return { 'allowed' => true } unless @config[:customer_id]
|
|
46
40
|
|
|
47
|
-
uri = URI("#{@config[:base_url] || 'https://
|
|
41
|
+
uri = URI("#{@config[:base_url] || 'https://api.agentbill.io'}/functions/v1/ai-cost-guard-router")
|
|
48
42
|
|
|
49
43
|
payload = {
|
|
50
44
|
api_key: @config[:api_key],
|
|
51
45
|
customer_id: @config[:customer_id],
|
|
52
46
|
model: model,
|
|
53
|
-
messages: messages
|
|
47
|
+
messages: messages,
|
|
48
|
+
daily_budget_override: @config[:daily_budget],
|
|
49
|
+
monthly_budget_override: @config[:monthly_budget]
|
|
54
50
|
}
|
|
55
51
|
|
|
56
52
|
begin
|
|
@@ -74,37 +70,8 @@ module AgentBill
|
|
|
74
70
|
end
|
|
75
71
|
end
|
|
76
72
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
payload = {
|
|
81
|
-
api_key: @config[:api_key],
|
|
82
|
-
customer_id: @config[:customer_id],
|
|
83
|
-
agent_id: @config[:agent_id],
|
|
84
|
-
event_name: event_name,
|
|
85
|
-
model: model,
|
|
86
|
-
provider: provider,
|
|
87
|
-
prompt_tokens: input_tokens,
|
|
88
|
-
completion_tokens: output_tokens,
|
|
89
|
-
latency_ms: latency_ms,
|
|
90
|
-
cost: cost
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
begin
|
|
94
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
|
95
|
-
http.use_ssl = true
|
|
96
|
-
http.read_timeout = 10
|
|
97
|
-
|
|
98
|
-
request = Net::HTTP::Post.new(uri.path)
|
|
99
|
-
request['Content-Type'] = 'application/json'
|
|
100
|
-
request.body = payload.to_json
|
|
101
|
-
|
|
102
|
-
http.request(request)
|
|
103
|
-
puts "[AgentBill Cost Guard] Usage tracked: $#{format('%.4f', cost)}" if @config[:debug]
|
|
104
|
-
rescue => e
|
|
105
|
-
puts "[AgentBill Cost Guard] Tracking failed: #{e.message}" if @config[:debug]
|
|
106
|
-
end
|
|
107
|
-
end
|
|
73
|
+
# v7.17.3: track_usage method DELETED - all tracking now goes through OTEL spans
|
|
74
|
+
# Use self.tracer.start_span() + span.finish() + self.tracer.flush_sync() instead
|
|
108
75
|
|
|
109
76
|
public
|
|
110
77
|
|
|
@@ -132,9 +99,59 @@ module AgentBill
|
|
|
132
99
|
raise error
|
|
133
100
|
end
|
|
134
101
|
|
|
102
|
+
# v7.6.10: Check for cached response from semantic cache
|
|
103
|
+
# CRITICAL FIX: Router sends cost_saved/tokens_saved at top level
|
|
104
|
+
if validation['cached'] && validation['response_data']
|
|
105
|
+
puts "[AgentBill] ✓ Cache hit - returning cached response" if config[:debug]
|
|
106
|
+
|
|
107
|
+
# v7.6.10 FIX: Check both nested 'cache'/'cache_info' AND top-level fields
|
|
108
|
+
cache_info = validation['cache'] || validation['cache_info'] || {}
|
|
109
|
+
cached_response = validation['response_data']
|
|
110
|
+
usage = cached_response['usage'] || {}
|
|
111
|
+
|
|
112
|
+
# v7.6.10 FIX: Extract from cache_info OR top-level validation
|
|
113
|
+
tokens_saved = cache_info['tokens_saved'] || validation['tokens_saved'] || usage['total_tokens'] || 0
|
|
114
|
+
cost_saved = cache_info['cost_saved'] || validation['cost_saved'] || 0
|
|
115
|
+
cached_input_tokens = usage['prompt_tokens'] || (tokens_saved * 0.2).to_i
|
|
116
|
+
cached_output_tokens = usage['completion_tokens'] || (tokens_saved - cached_input_tokens)
|
|
117
|
+
cached_total_tokens = tokens_saved > 0 ? tokens_saved : (cached_input_tokens + cached_output_tokens)
|
|
118
|
+
|
|
119
|
+
# v7.6.10 CRITICAL FIX: Set gen_ai.usage.* to 0 for cache hits
|
|
120
|
+
# This prevents OTEL ingestion from calculating cost for cached responses
|
|
121
|
+
span = tracer.start_span('openai.chat.completion', {
|
|
122
|
+
'gen_ai.system' => 'openai',
|
|
123
|
+
'gen_ai.request.model' => model,
|
|
124
|
+
'gen_ai.operation.name' => 'chat',
|
|
125
|
+
'model' => model,
|
|
126
|
+
'provider' => 'openai',
|
|
127
|
+
'agentbill.from_cache' => true,
|
|
128
|
+
'agentbill.cache_hit' => true,
|
|
129
|
+
# OTEL cost calculation attributes - set to 0 for cache hits
|
|
130
|
+
'gen_ai.usage.input_tokens' => 0,
|
|
131
|
+
'gen_ai.usage.output_tokens' => 0,
|
|
132
|
+
'gen_ai.usage.total_tokens' => 0,
|
|
133
|
+
# Informational attributes - actual cached token counts
|
|
134
|
+
'agentbill.cached_input_tokens' => cached_input_tokens,
|
|
135
|
+
'agentbill.cached_output_tokens' => cached_output_tokens,
|
|
136
|
+
'agentbill.cached_total_tokens' => cached_total_tokens,
|
|
137
|
+
'agentbill.tokens_saved' => tokens_saved,
|
|
138
|
+
'agentbill.cost_saved' => cost_saved
|
|
139
|
+
})
|
|
140
|
+
span.set_status(0)
|
|
141
|
+
span.finish
|
|
142
|
+
tracer.flush
|
|
143
|
+
|
|
144
|
+
return cached_response
|
|
145
|
+
end
|
|
146
|
+
|
|
135
147
|
# Phase 2: Execute AI call
|
|
136
148
|
start_time = Time.now
|
|
137
149
|
span = tracer.start_span('openai.chat.completion', {
|
|
150
|
+
# ✅ OTEL GenAI compliant attributes
|
|
151
|
+
'gen_ai.system' => 'openai',
|
|
152
|
+
'gen_ai.request.model' => model,
|
|
153
|
+
'gen_ai.operation.name' => 'chat',
|
|
154
|
+
# ⚠️ Backward compatibility
|
|
138
155
|
'model' => model,
|
|
139
156
|
'provider' => 'openai'
|
|
140
157
|
})
|
|
@@ -143,14 +160,19 @@ module AgentBill
|
|
|
143
160
|
response = original_method.call(params)
|
|
144
161
|
latency = ((Time.now - start_time) * 1000).round
|
|
145
162
|
|
|
146
|
-
#
|
|
163
|
+
# NOTE: wrap() only creates OTEL spans, NOT signals
|
|
164
|
+
# Signals should be created explicitly via track_signal()
|
|
147
165
|
input_tokens = response.dig(:usage, :prompt_tokens) || 0
|
|
148
166
|
output_tokens = response.dig(:usage, :completion_tokens) || 0
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
config[:_client].send(:track_usage, model, 'openai', input_tokens, output_tokens, latency, cost, event_name)
|
|
167
|
+
# v9.1.0: Cost calculated server-side from token counts in span
|
|
152
168
|
|
|
153
169
|
span.set_attributes({
|
|
170
|
+
# ✅ OTEL GenAI compliant attributes
|
|
171
|
+
'gen_ai.usage.input_tokens' => input_tokens,
|
|
172
|
+
'gen_ai.usage.output_tokens' => output_tokens,
|
|
173
|
+
'gen_ai.usage.total_tokens' => response.dig(:usage, :total_tokens),
|
|
174
|
+
'gen_ai.response.id' => response.dig(:id),
|
|
175
|
+
# ⚠️ Backward compatibility
|
|
154
176
|
'response.prompt_tokens' => input_tokens,
|
|
155
177
|
'response.completion_tokens' => output_tokens,
|
|
156
178
|
'response.total_tokens' => response.dig(:usage, :total_tokens),
|
|
@@ -158,16 +180,25 @@ module AgentBill
|
|
|
158
180
|
})
|
|
159
181
|
span.set_status(0)
|
|
160
182
|
|
|
161
|
-
puts "[AgentBill
|
|
183
|
+
puts "[AgentBill] ✓ OpenAI call: #{input_tokens}in/#{output_tokens}out tokens (cost calculated server-side)" if config[:debug]
|
|
184
|
+
|
|
185
|
+
# v7.5.0: Cache AI response for semantic caching
|
|
186
|
+
response_content = response.dig(:choices, 0, :message, :content) || ''
|
|
187
|
+
# v9.1.0: Fixed prompt_hash to match backend algorithm (SHA-256 of JSON, full hash)
|
|
188
|
+
prompt_text = JSON.generate(messages)
|
|
189
|
+
prompt_hash = Digest::SHA256.hexdigest(prompt_text)
|
|
190
|
+
config[:_client].send(:cache_response, model, prompt_hash, response_content, input_tokens + output_tokens, 0)
|
|
191
|
+
|
|
162
192
|
response
|
|
163
193
|
rescue => e
|
|
164
194
|
span.set_status(1, e.message)
|
|
165
195
|
raise
|
|
166
196
|
ensure
|
|
167
197
|
span.finish
|
|
198
|
+
# Auto-flush spans to prevent data loss
|
|
199
|
+
tracer.flush
|
|
168
200
|
end
|
|
169
201
|
end
|
|
170
|
-
|
|
171
202
|
# Store reference to self for helper methods
|
|
172
203
|
@config[:_client] = self
|
|
173
204
|
client
|
|
@@ -196,6 +227,11 @@ module AgentBill
|
|
|
196
227
|
# Phase 2: Execute AI call
|
|
197
228
|
start_time = Time.now
|
|
198
229
|
span = tracer.start_span('anthropic.message', {
|
|
230
|
+
# ✅ OTEL GenAI compliant attributes
|
|
231
|
+
'gen_ai.system' => 'anthropic',
|
|
232
|
+
'gen_ai.request.model' => model,
|
|
233
|
+
'gen_ai.operation.name' => 'chat',
|
|
234
|
+
# ⚠️ Backward compatibility
|
|
199
235
|
'model' => model,
|
|
200
236
|
'provider' => 'anthropic'
|
|
201
237
|
})
|
|
@@ -204,30 +240,34 @@ module AgentBill
|
|
|
204
240
|
response = original_method.call(params)
|
|
205
241
|
latency = ((Time.now - start_time) * 1000).round
|
|
206
242
|
|
|
207
|
-
#
|
|
243
|
+
# NOTE: wrap() only creates OTEL spans, NOT signals
|
|
208
244
|
input_tokens = response.dig(:usage, :input_tokens) || 0
|
|
209
245
|
output_tokens = response.dig(:usage, :output_tokens) || 0
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
config[:_client].send(:track_usage, model, 'anthropic', input_tokens, output_tokens, latency, cost)
|
|
246
|
+
# v9.1.0: Cost calculated server-side from token counts in span
|
|
213
247
|
|
|
214
248
|
span.set_attributes({
|
|
249
|
+
# ✅ OTEL GenAI compliant attributes
|
|
250
|
+
'gen_ai.usage.input_tokens' => input_tokens,
|
|
251
|
+
'gen_ai.usage.output_tokens' => output_tokens,
|
|
252
|
+
'gen_ai.response.id' => response.dig(:id),
|
|
253
|
+
# ⚠️ Backward compatibility
|
|
215
254
|
'response.input_tokens' => input_tokens,
|
|
216
255
|
'response.output_tokens' => output_tokens,
|
|
217
256
|
'latency_ms' => latency
|
|
218
257
|
})
|
|
219
258
|
span.set_status(0)
|
|
220
259
|
|
|
221
|
-
puts "[AgentBill
|
|
260
|
+
puts "[AgentBill] ✓ OTEL span created: $#{format('%.4f', cost)} (use track_signal() for signals)" if config[:debug]
|
|
222
261
|
response
|
|
223
262
|
rescue => e
|
|
224
263
|
span.set_status(1, e.message)
|
|
225
264
|
raise
|
|
226
265
|
ensure
|
|
227
266
|
span.finish
|
|
267
|
+
# Auto-flush spans
|
|
268
|
+
tracer.flush
|
|
228
269
|
end
|
|
229
270
|
end
|
|
230
|
-
|
|
231
271
|
# Store reference to self for helper methods
|
|
232
272
|
@config[:_client] = self
|
|
233
273
|
client
|
|
@@ -274,25 +314,59 @@ module AgentBill
|
|
|
274
314
|
def track_signal(**params)
|
|
275
315
|
raise ArgumentError, "event_name is required" unless params[:event_name]
|
|
276
316
|
|
|
277
|
-
|
|
317
|
+
# v7.17.1: Unified OTEL model - route through otel-collector
|
|
318
|
+
uri = URI("#{@config[:base_url] || 'https://api.agentbill.io'}/functions/v1/otel-collector")
|
|
278
319
|
|
|
279
|
-
#
|
|
280
|
-
params[:
|
|
320
|
+
# Generate trace context
|
|
321
|
+
trace_id = params[:trace_id] || SecureRandom.hex(16)
|
|
322
|
+
span_id = params[:span_id] || SecureRandom.hex(8)
|
|
323
|
+
now_ns = (Time.now.to_f * 1_000_000_000).to_i
|
|
281
324
|
|
|
282
|
-
# Auto-fill customer_id
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
uuid_regex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
286
|
-
is_uuid = @config[:customer_id].match?(uuid_regex)
|
|
287
|
-
if is_uuid
|
|
288
|
-
params[:customer_id] = @config[:customer_id]
|
|
289
|
-
else
|
|
290
|
-
params[:customer_external_id] = @config[:customer_id]
|
|
291
|
-
end
|
|
292
|
-
end
|
|
325
|
+
# Auto-fill customer_id from config if not provided
|
|
326
|
+
customer_id = params[:customer_id] || params[:customer_external_id] || @config[:customer_id] || ""
|
|
327
|
+
agent_id = params[:agent_id] || params[:agent_external_id] || @config[:agent_id] || ""
|
|
293
328
|
|
|
294
|
-
#
|
|
295
|
-
|
|
329
|
+
# Build OTEL-compliant span attributes
|
|
330
|
+
attributes = [
|
|
331
|
+
{ "key" => "agentbill.event_name", "value" => { "stringValue" => params[:event_name] } },
|
|
332
|
+
{ "key" => "agentbill.is_business_event", "value" => { "boolValue" => true } },
|
|
333
|
+
{ "key" => "agentbill.data_source", "value" => { "stringValue" => "ruby-sdk" } },
|
|
334
|
+
]
|
|
335
|
+
|
|
336
|
+
attributes << { "key" => "agentbill.customer_id", "value" => { "stringValue" => customer_id.to_s } } if customer_id && customer_id != ""
|
|
337
|
+
attributes << { "key" => "agentbill.agent_id", "value" => { "stringValue" => agent_id.to_s } } if agent_id && agent_id != ""
|
|
338
|
+
attributes << { "key" => "agentbill.revenue", "value" => { "doubleValue" => params[:revenue] } } if params[:revenue]
|
|
339
|
+
attributes << { "key" => "agentbill.currency", "value" => { "stringValue" => params[:currency] || "USD" } } if params[:revenue]
|
|
340
|
+
attributes << { "key" => "agentbill.model", "value" => { "stringValue" => params[:model] } } if params[:model]
|
|
341
|
+
attributes << { "key" => "agentbill.session_id", "value" => { "stringValue" => params[:session_id] } } if params[:session_id]
|
|
342
|
+
attributes << { "key" => "agentbill.metadata", "value" => { "stringValue" => params[:metadata].to_json } } if params[:metadata]
|
|
343
|
+
|
|
344
|
+
# Build OTEL payload
|
|
345
|
+
otel_payload = {
|
|
346
|
+
"resourceSpans" => [{
|
|
347
|
+
"resource" => {
|
|
348
|
+
"attributes" => [
|
|
349
|
+
{ "key" => "service.name", "value" => { "stringValue" => "agentbill-ruby-sdk" } },
|
|
350
|
+
{ "key" => "agentbill.customer_id", "value" => { "stringValue" => customer_id.to_s } },
|
|
351
|
+
{ "key" => "agentbill.agent_id", "value" => { "stringValue" => agent_id.to_s } },
|
|
352
|
+
]
|
|
353
|
+
},
|
|
354
|
+
"scopeSpans" => [{
|
|
355
|
+
"scope" => { "name" => "agentbill.signals", "version" => "7.17.3" },
|
|
356
|
+
"spans" => [{
|
|
357
|
+
"traceId" => trace_id,
|
|
358
|
+
"spanId" => span_id,
|
|
359
|
+
"parentSpanId" => params[:parent_span_id] || "",
|
|
360
|
+
"name" => "agentbill.trace.signal",
|
|
361
|
+
"kind" => 1,
|
|
362
|
+
"startTimeUnixNano" => now_ns.to_s,
|
|
363
|
+
"endTimeUnixNano" => now_ns.to_s,
|
|
364
|
+
"attributes" => attributes,
|
|
365
|
+
"status" => { "code" => 1 }
|
|
366
|
+
}]
|
|
367
|
+
}]
|
|
368
|
+
}]
|
|
369
|
+
}
|
|
296
370
|
|
|
297
371
|
begin
|
|
298
372
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
@@ -301,13 +375,12 @@ module AgentBill
|
|
|
301
375
|
request = Net::HTTP::Post.new(uri.path)
|
|
302
376
|
request['X-API-Key'] = @config[:api_key]
|
|
303
377
|
request['Content-Type'] = 'application/json'
|
|
304
|
-
request.body =
|
|
378
|
+
request.body = otel_payload.to_json
|
|
305
379
|
|
|
306
380
|
response = http.request(request)
|
|
307
381
|
|
|
308
382
|
if @config[:debug]
|
|
309
|
-
|
|
310
|
-
puts "[AgentBill] Signal tracked: #{params[:event_name]}#{trace_info}"
|
|
383
|
+
puts "[AgentBill] Signal tracked via OTEL: #{params[:event_name]} (trace: #{trace_id})"
|
|
311
384
|
end
|
|
312
385
|
|
|
313
386
|
response.code == '200'
|
|
@@ -319,8 +392,122 @@ module AgentBill
|
|
|
319
392
|
end
|
|
320
393
|
end
|
|
321
394
|
|
|
395
|
+
# Track a conversion event for prompt profitability analysis
|
|
396
|
+
# Links conversions to AI prompts to calculate ROI per prompt
|
|
397
|
+
#
|
|
398
|
+
# @param event_type [String] Type of conversion (e.g., 'purchase', 'signup', 'trial_start')
|
|
399
|
+
# @param event_value [Float] Revenue amount from the conversion
|
|
400
|
+
# @param signal_id [String, nil] Optional UUID linking to specific AI signal/prompt
|
|
401
|
+
# @param session_id [String, nil] Optional session identifier
|
|
402
|
+
# @param attribution_window_hours [Integer] Time window for attribution (default: 24 hours)
|
|
403
|
+
# @param currency [String] Currency code (default: 'USD')
|
|
404
|
+
# @param metadata [Hash, nil] Optional additional data
|
|
405
|
+
# @return [Hash] Response with success status and conversion_id
|
|
406
|
+
#
|
|
407
|
+
# Example:
|
|
408
|
+
# # Track a purchase conversion
|
|
409
|
+
# result = agentbill.track_conversion(
|
|
410
|
+
# event_type: 'purchase',
|
|
411
|
+
# event_value: 99.99,
|
|
412
|
+
# currency: 'USD'
|
|
413
|
+
# )
|
|
414
|
+
#
|
|
415
|
+
# # Link conversion to specific AI prompt
|
|
416
|
+
# result = agentbill.track_conversion(
|
|
417
|
+
# event_type: 'trial_signup',
|
|
418
|
+
# event_value: 29.99,
|
|
419
|
+
# signal_id: 'signal-uuid-from-prompt',
|
|
420
|
+
# session_id: 'session-123'
|
|
421
|
+
# )
|
|
422
|
+
def track_conversion(event_type:, event_value:, signal_id: nil, session_id: nil, attribution_window_hours: 24, currency: 'USD', metadata: nil)
|
|
423
|
+
uri = URI("#{@config[:base_url] || 'https://api.agentbill.io'}/functions/v1/track-conversion")
|
|
424
|
+
|
|
425
|
+
payload = {
|
|
426
|
+
api_key: @config[:api_key],
|
|
427
|
+
customer_id: @config[:customer_id],
|
|
428
|
+
event_type: event_type,
|
|
429
|
+
event_value: event_value,
|
|
430
|
+
signal_id: signal_id,
|
|
431
|
+
session_id: session_id,
|
|
432
|
+
attribution_window_hours: attribution_window_hours,
|
|
433
|
+
currency: currency,
|
|
434
|
+
metadata: metadata
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
begin
|
|
438
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
439
|
+
http.use_ssl = true
|
|
440
|
+
|
|
441
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
442
|
+
request['Content-Type'] = 'application/json'
|
|
443
|
+
request.body = payload.to_json
|
|
444
|
+
|
|
445
|
+
response = http.request(request)
|
|
446
|
+
data = JSON.parse(response.body)
|
|
447
|
+
|
|
448
|
+
if response.code != '200'
|
|
449
|
+
return {
|
|
450
|
+
success: false,
|
|
451
|
+
error: data['error'] || "HTTP #{response.code}"
|
|
452
|
+
}
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
if @config[:debug]
|
|
456
|
+
puts "[AgentBill] Conversion tracked: #{event_type} = $#{event_value} (ID: #{data['conversion_id']})"
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
{
|
|
460
|
+
success: true,
|
|
461
|
+
conversion_id: data['conversion_id']
|
|
462
|
+
}
|
|
463
|
+
rescue => e
|
|
464
|
+
if @config[:debug]
|
|
465
|
+
puts "[AgentBill] Failed to track conversion: #{e.message}"
|
|
466
|
+
end
|
|
467
|
+
{
|
|
468
|
+
success: false,
|
|
469
|
+
error: e.message
|
|
470
|
+
}
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
|
|
322
474
|
def flush
|
|
323
475
|
@tracer.flush
|
|
324
476
|
end
|
|
477
|
+
|
|
478
|
+
private
|
|
479
|
+
|
|
480
|
+
# Cache AI response for semantic caching (v7.5.0)
|
|
481
|
+
def cache_response(model, prompt_hash, response_content, tokens_used = 0, cost = 0.0)
|
|
482
|
+
return if response_content.nil? || response_content.empty?
|
|
483
|
+
|
|
484
|
+
uri = URI("#{@config[:base_url] || 'https://api.agentbill.io'}/functions/v1/cache-ai-response")
|
|
485
|
+
|
|
486
|
+
payload = {
|
|
487
|
+
api_key: @config[:api_key],
|
|
488
|
+
prompt_hash: prompt_hash,
|
|
489
|
+
response_content: response_content,
|
|
490
|
+
model: model,
|
|
491
|
+
tokens_used: tokens_used,
|
|
492
|
+
cost: cost,
|
|
493
|
+
customer_id: @config[:customer_id],
|
|
494
|
+
agent_id: @config[:agent_id]
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
begin
|
|
498
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
499
|
+
http.use_ssl = true
|
|
500
|
+
http.read_timeout = 5
|
|
501
|
+
|
|
502
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
503
|
+
request['Content-Type'] = 'application/json'
|
|
504
|
+
request.body = payload.to_json
|
|
505
|
+
|
|
506
|
+
http.request(request)
|
|
507
|
+
puts "[AgentBill] Response cached for semantic caching" if @config[:debug]
|
|
508
|
+
rescue => e
|
|
509
|
+
puts "[AgentBill] Cache response failed: #{e.message}" if @config[:debug]
|
|
510
|
+
end
|
|
511
|
+
end
|
|
325
512
|
end
|
|
326
513
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: agentbill-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 9.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- AgentBill
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-04-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -65,12 +65,26 @@ files:
|
|
|
65
65
|
- agentbill.gemspec
|
|
66
66
|
- examples/anthropic_basic.rb
|
|
67
67
|
- examples/manual_otel_tracking.rb
|
|
68
|
+
- examples/ollama_basic.rb
|
|
68
69
|
- examples/openai_basic.rb
|
|
69
70
|
- examples/openai_custom_event.rb
|
|
71
|
+
- examples/perplexity_basic.rb
|
|
70
72
|
- examples/zero_config.rb
|
|
71
73
|
- lib/agentbill.rb
|
|
74
|
+
- lib/agentbill/agents.rb
|
|
75
|
+
- lib/agentbill/attributes.rb
|
|
76
|
+
- lib/agentbill/customers.rb
|
|
77
|
+
- lib/agentbill/distributed.rb
|
|
78
|
+
- lib/agentbill/exceptions.rb
|
|
79
|
+
- lib/agentbill/ollama_wrapper.rb
|
|
80
|
+
- lib/agentbill/orders.rb
|
|
81
|
+
- lib/agentbill/perplexity_wrapper.rb
|
|
82
|
+
- lib/agentbill/signal_types.rb
|
|
83
|
+
- lib/agentbill/signals.rb
|
|
72
84
|
- lib/agentbill/tracer.rb
|
|
85
|
+
- lib/agentbill/tracing.rb
|
|
73
86
|
- lib/agentbill/version.rb
|
|
87
|
+
- lib/agentbill/wrappers.rb
|
|
74
88
|
homepage: https://github.com/Agent-Bill/Ruby
|
|
75
89
|
licenses:
|
|
76
90
|
- MIT
|