agentbill-sdk 1.0.1 → 1.0.3
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/publish.yml +56 -0
- data/CHANGELOG.md +6 -0
- data/README.md +2 -2
- data/agentbill.gemspec +1 -1
- data/examples/manual_otel_tracking.rb +45 -0
- data/examples/zero_config.rb +69 -0
- data/lib/agentbill/version.rb +1 -1
- data/lib/agentbill.rb +193 -21
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eaa02ddbb2ec18543984f6db7fa7e579b30f94e4505469f6e1eb72cfb13b4048
|
|
4
|
+
data.tar.gz: 9ef5bdf26669e2b5f2d5594960c9b8f946a6e47875c7461dec37b4f3c477a79c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4401d68f0d95b0d4df7f19c69c90c30ff497fcde8a9aa5177c24a2c52664610de3a1a5eff3407c6d61950d788265eca3c5d51eb05e1a2cde6a2c94337869fbd0
|
|
7
|
+
data.tar.gz: 65202696532413e194c27a8aafeb1e3f0874b58863408a13455fbe07a9e115c73687c29df8a9b58b98b987dff329fb000def1514bfa66454a2abbe03f513b4cb
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: Publish to RubyGems
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'ruby-v*'
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
version:
|
|
10
|
+
description: 'Version to publish'
|
|
11
|
+
required: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
publish:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
id-token: write
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- name: Set up Ruby
|
|
24
|
+
uses: ruby/setup-ruby@v1
|
|
25
|
+
with:
|
|
26
|
+
ruby-version: '3.2'
|
|
27
|
+
bundler-cache: true
|
|
28
|
+
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: bundle install
|
|
31
|
+
|
|
32
|
+
- name: Run tests
|
|
33
|
+
run: bundle exec rake test
|
|
34
|
+
|
|
35
|
+
- name: Build gem
|
|
36
|
+
run: gem build agentbill.gemspec
|
|
37
|
+
|
|
38
|
+
- name: Publish to RubyGems
|
|
39
|
+
run: |
|
|
40
|
+
mkdir -p $HOME/.gem
|
|
41
|
+
touch $HOME/.gem/credentials
|
|
42
|
+
chmod 0600 $HOME/.gem/credentials
|
|
43
|
+
printf -- "---\n:rubygems_api_key: ${RUBYGEMS_API_KEY}\n" > $HOME/.gem/credentials
|
|
44
|
+
gem push *.gem
|
|
45
|
+
env:
|
|
46
|
+
RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
|
|
47
|
+
|
|
48
|
+
- name: Create GitHub Release
|
|
49
|
+
uses: actions/create-release@v1
|
|
50
|
+
env:
|
|
51
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
52
|
+
with:
|
|
53
|
+
tag_name: ${{ github.ref }}
|
|
54
|
+
release_name: Ruby SDK ${{ github.ref }}
|
|
55
|
+
draft: false
|
|
56
|
+
prerelease: false
|
data/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
+
# Force sync - Updated to 1.0.2
|
|
2
3
|
|
|
3
4
|
All notable changes to this project will be documented in this file.
|
|
4
5
|
|
|
5
6
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
7
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
8
|
|
|
9
|
+
## [1.0.2] - 2025-10-27
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Version bump for consistency across SDKs
|
|
13
|
+
|
|
8
14
|
## [1.0.0] - 2025-10-21
|
|
9
15
|
|
|
10
16
|
### Added
|
data/README.md
CHANGED
|
@@ -4,10 +4,10 @@ OpenTelemetry-based SDK for automatically tracking and billing AI agent usage.
|
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
### From
|
|
7
|
+
### From RubyGems (Recommended)
|
|
8
8
|
```ruby
|
|
9
9
|
# In your Gemfile
|
|
10
|
-
gem 'agentbill
|
|
10
|
+
gem 'agentbill-sdk'
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
### From RubyGems
|
data/agentbill.gemspec
CHANGED
|
@@ -4,7 +4,7 @@ Gem::Specification.new do |spec|
|
|
|
4
4
|
spec.name = "agentbill-sdk"
|
|
5
5
|
spec.version = AgentBill::VERSION
|
|
6
6
|
spec.authors = ["AgentBill"]
|
|
7
|
-
spec.email = ["
|
|
7
|
+
spec.email = ["dominic@agentbill.io"]
|
|
8
8
|
|
|
9
9
|
spec.summary = "OpenTelemetry-based SDK for tracking AI agent usage and billing"
|
|
10
10
|
spec.description = "Automatically track and bill AI agent usage with zero-config instrumentation for OpenAI, Anthropic, and more"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'agentbill'
|
|
2
|
+
|
|
3
|
+
# Initialize AgentBill
|
|
4
|
+
agentbill = AgentBill::Client.init({
|
|
5
|
+
api_key: ENV['AGENTBILL_API_KEY'] || 'your-api-key',
|
|
6
|
+
base_url: ENV['AGENTBILL_BASE_URL'],
|
|
7
|
+
customer_id: 'customer-123',
|
|
8
|
+
debug: true
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
# Start a span for a database query
|
|
12
|
+
span = agentbill.tracer.start_span('database_query', {
|
|
13
|
+
'db.system' => 'postgresql',
|
|
14
|
+
'db.operation' => 'SELECT',
|
|
15
|
+
'db.table' => 'users'
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
# Simulate work
|
|
19
|
+
sleep(0.1)
|
|
20
|
+
|
|
21
|
+
# Add more attributes during execution
|
|
22
|
+
span.set_attributes({
|
|
23
|
+
'db.rows_returned' => 42,
|
|
24
|
+
'query.duration_ms' => 95
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
# Set status (1 = success, 2 = error)
|
|
28
|
+
span.set_status(1)
|
|
29
|
+
span.end
|
|
30
|
+
|
|
31
|
+
# Start another span for an API call
|
|
32
|
+
api_span = agentbill.tracer.start_span('external_api_call', {
|
|
33
|
+
'http.method' => 'POST',
|
|
34
|
+
'http.url' => 'https://api.example.com/endpoint',
|
|
35
|
+
'http.status_code' => 200
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
sleep(0.2)
|
|
39
|
+
api_span.set_status(1)
|
|
40
|
+
api_span.end
|
|
41
|
+
|
|
42
|
+
# Flush all spans to AgentBill
|
|
43
|
+
agentbill.flush
|
|
44
|
+
|
|
45
|
+
puts "✅ OTEL spans tracked successfully!"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
Zero-Config AI Cost Guard Example
|
|
3
|
+
|
|
4
|
+
Just wrap your AI client - that's it!
|
|
5
|
+
|
|
6
|
+
What happens automatically:
|
|
7
|
+
- Provider detection (OpenAI, Anthropic, etc.)
|
|
8
|
+
- Budget validation BEFORE spending (when daily_budget or monthly_budget is set)
|
|
9
|
+
- Automatic blocking if budget would be exceeded
|
|
10
|
+
- Usage tracking AFTER completion
|
|
11
|
+
=end
|
|
12
|
+
|
|
13
|
+
require 'agentbill'
|
|
14
|
+
require 'openai'
|
|
15
|
+
|
|
16
|
+
# 1. Initialize AgentBill with your API key and budget
|
|
17
|
+
agentbill = AgentBill::Client.init({
|
|
18
|
+
api_key: ENV['AGENTBILL_API_KEY'],
|
|
19
|
+
customer_id: 'customer-123',
|
|
20
|
+
daily_budget: 10.00, # Optional: $10/day limit (enables Cost Guard)
|
|
21
|
+
monthly_budget: 200.00, # Optional: $200/month limit (enables Cost Guard)
|
|
22
|
+
debug: true
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
# 2. Wrap your AI client - provider auto-detected!
|
|
26
|
+
openai = agentbill.wrap_openai(OpenAI::Client.new(
|
|
27
|
+
access_token: ENV['OPENAI_API_KEY']
|
|
28
|
+
))
|
|
29
|
+
|
|
30
|
+
# 3. Use normally - Cost Guard protection happens automatically
|
|
31
|
+
begin
|
|
32
|
+
response = openai.chat({
|
|
33
|
+
model: 'gpt-4o-mini',
|
|
34
|
+
messages: [
|
|
35
|
+
{ role: 'user', content: 'What is the capital of France?' }
|
|
36
|
+
]
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
puts '✅ Response: ' + response['choices'][0]['message']['content']
|
|
40
|
+
puts '💰 Cost tracked automatically'
|
|
41
|
+
puts '🛡️ Budget validated BEFORE spending'
|
|
42
|
+
|
|
43
|
+
rescue => e
|
|
44
|
+
if e.respond_to?(:code)
|
|
45
|
+
case e.code
|
|
46
|
+
when 'BUDGET_EXCEEDED'
|
|
47
|
+
puts '❌ Budget limit reached - request blocked by Cost Guard'
|
|
48
|
+
when 'RATE_LIMIT_EXCEEDED'
|
|
49
|
+
puts '❌ Rate limit reached - request blocked by Cost Guard'
|
|
50
|
+
else
|
|
51
|
+
raise e
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
raise e
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
=begin
|
|
59
|
+
That's it! Zero configuration needed.
|
|
60
|
+
|
|
61
|
+
Cost Guard Features:
|
|
62
|
+
- ✅ Hard budget limits (daily/monthly) - request blocked BEFORE spending
|
|
63
|
+
- ✅ Real-time cost estimation and validation
|
|
64
|
+
- ✅ Automatic usage tracking
|
|
65
|
+
- ✅ Works across all providers (OpenAI, Anthropic, Bedrock, Azure, Mistral, Google AI)
|
|
66
|
+
|
|
67
|
+
To disable Cost Guard: Simply remove daily_budget and monthly_budget from init.
|
|
68
|
+
Advanced features (caching, fallback, etc.) coming in Pro tier.
|
|
69
|
+
=end
|
data/lib/agentbill/version.rb
CHANGED
data/lib/agentbill.rb
CHANGED
|
@@ -17,29 +17,142 @@ module AgentBill
|
|
|
17
17
|
new(config)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def estimate_tokens(text)
|
|
23
|
+
# Simple token estimation: ~4 chars per token
|
|
24
|
+
[1, text.to_s.length / 4].max
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def estimate_cost(model, input_tokens, output_tokens)
|
|
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
|
|
43
|
+
|
|
44
|
+
def validate_request(model, messages, estimated_output_tokens = 1000)
|
|
45
|
+
return { 'allowed' => true } unless @config[:daily_budget] || @config[:monthly_budget]
|
|
46
|
+
|
|
47
|
+
uri = URI("#{@config[:base_url] || 'https://bgwyprqxtdreuutzpbgw.supabase.co'}/functions/v1/ai-cost-guard-router")
|
|
48
|
+
|
|
49
|
+
payload = {
|
|
50
|
+
api_key: @config[:api_key],
|
|
51
|
+
customer_id: @config[:customer_id],
|
|
52
|
+
model: model,
|
|
53
|
+
messages: messages
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
begin
|
|
57
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
58
|
+
http.use_ssl = true
|
|
59
|
+
http.read_timeout = 10
|
|
60
|
+
|
|
61
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
62
|
+
request['Content-Type'] = 'application/json'
|
|
63
|
+
request.body = payload.to_json
|
|
64
|
+
|
|
65
|
+
response = http.request(request)
|
|
66
|
+
result = JSON.parse(response.body)
|
|
67
|
+
|
|
68
|
+
puts "[AgentBill Cost Guard] Router response: #{result}" if @config[:debug]
|
|
69
|
+
puts "[AgentBill] Tier: #{result['tier']}, Mode: #{result['mode']}" if @config[:debug] && result['tier']
|
|
70
|
+
result
|
|
71
|
+
rescue => e
|
|
72
|
+
puts "[AgentBill Cost Guard] Router failed: #{e.message}" if @config[:debug]
|
|
73
|
+
{ 'allowed' => true } # Fail open
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def track_usage(model, provider, input_tokens, output_tokens, latency_ms, cost)
|
|
78
|
+
uri = URI("#{@config[:base_url] || 'https://bgwyprqxtdreuutzpbgw.supabase.co'}/functions/v1/track-ai-usage")
|
|
79
|
+
|
|
80
|
+
payload = {
|
|
81
|
+
api_key: @config[:api_key],
|
|
82
|
+
customer_id: @config[:customer_id],
|
|
83
|
+
model: model,
|
|
84
|
+
provider: provider,
|
|
85
|
+
prompt_tokens: input_tokens,
|
|
86
|
+
completion_tokens: output_tokens,
|
|
87
|
+
latency_ms: latency_ms,
|
|
88
|
+
cost: cost
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
begin
|
|
92
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
93
|
+
http.use_ssl = true
|
|
94
|
+
http.read_timeout = 10
|
|
95
|
+
|
|
96
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
97
|
+
request['Content-Type'] = 'application/json'
|
|
98
|
+
request.body = payload.to_json
|
|
99
|
+
|
|
100
|
+
http.request(request)
|
|
101
|
+
puts "[AgentBill Cost Guard] Usage tracked: $#{format('%.4f', cost)}" if @config[:debug]
|
|
102
|
+
rescue => e
|
|
103
|
+
puts "[AgentBill Cost Guard] Tracking failed: #{e.message}" if @config[:debug]
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
public
|
|
108
|
+
|
|
20
109
|
def wrap_openai(client)
|
|
21
110
|
original_method = client.method(:chat)
|
|
111
|
+
config = @config
|
|
112
|
+
tracer = @tracer
|
|
22
113
|
|
|
23
114
|
client.define_singleton_method(:chat) do |params|
|
|
24
|
-
|
|
115
|
+
model = params[:model] || 'unknown'
|
|
116
|
+
messages = params[:messages] || []
|
|
117
|
+
max_tokens = params[:max_tokens] || params[:max_completion_tokens] || 1000
|
|
118
|
+
|
|
119
|
+
# Phase 1: Validate budget BEFORE API call
|
|
120
|
+
validation = config[:_client].send(:validate_request, model, messages, max_tokens)
|
|
121
|
+
unless validation['allowed']
|
|
122
|
+
error_msg = validation['reason'] || 'Budget limit reached'
|
|
123
|
+
puts "[AgentBill Cost Guard] ❌ Request blocked: #{error_msg}" if config[:debug]
|
|
124
|
+
error = StandardError.new(error_msg)
|
|
125
|
+
error.define_singleton_method(:code) { 'BUDGET_EXCEEDED' }
|
|
126
|
+
raise error
|
|
127
|
+
end
|
|
25
128
|
|
|
26
|
-
|
|
27
|
-
|
|
129
|
+
# Phase 2: Execute AI call
|
|
130
|
+
start_time = Time.now
|
|
131
|
+
span = tracer.start_span('openai.chat.completion', {
|
|
132
|
+
'model' => model,
|
|
28
133
|
'provider' => 'openai'
|
|
29
134
|
})
|
|
30
135
|
|
|
31
136
|
begin
|
|
32
137
|
response = original_method.call(params)
|
|
33
|
-
|
|
34
138
|
latency = ((Time.now - start_time) * 1000).round
|
|
139
|
+
|
|
140
|
+
# Phase 3: Track actual usage
|
|
141
|
+
input_tokens = response.dig(:usage, :prompt_tokens) || 0
|
|
142
|
+
output_tokens = response.dig(:usage, :completion_tokens) || 0
|
|
143
|
+
cost = config[:_client].send(:estimate_cost, model, input_tokens, output_tokens)
|
|
144
|
+
|
|
145
|
+
config[:_client].send(:track_usage, model, 'openai', input_tokens, output_tokens, latency, cost)
|
|
146
|
+
|
|
35
147
|
span.set_attributes({
|
|
36
|
-
'response.prompt_tokens' =>
|
|
37
|
-
'response.completion_tokens' =>
|
|
148
|
+
'response.prompt_tokens' => input_tokens,
|
|
149
|
+
'response.completion_tokens' => output_tokens,
|
|
38
150
|
'response.total_tokens' => response.dig(:usage, :total_tokens),
|
|
39
151
|
'latency_ms' => latency
|
|
40
152
|
})
|
|
41
153
|
span.set_status(0)
|
|
42
154
|
|
|
155
|
+
puts "[AgentBill Cost Guard] ✓ Protected call completed: $#{format('%.4f', cost)}" if config[:debug]
|
|
43
156
|
response
|
|
44
157
|
rescue => e
|
|
45
158
|
span.set_status(1, e.message)
|
|
@@ -49,31 +162,57 @@ module AgentBill
|
|
|
49
162
|
end
|
|
50
163
|
end
|
|
51
164
|
|
|
165
|
+
# Store reference to self for helper methods
|
|
166
|
+
@config[:_client] = self
|
|
52
167
|
client
|
|
53
168
|
end
|
|
54
169
|
|
|
55
170
|
def wrap_anthropic(client)
|
|
56
171
|
original_method = client.method(:messages)
|
|
172
|
+
config = @config
|
|
173
|
+
tracer = @tracer
|
|
57
174
|
|
|
58
175
|
client.define_singleton_method(:messages) do |params|
|
|
59
|
-
|
|
176
|
+
model = params[:model] || 'unknown'
|
|
177
|
+
messages = params[:messages] || []
|
|
178
|
+
max_tokens = params[:max_tokens] || 1000
|
|
179
|
+
|
|
180
|
+
# Phase 1: Validate budget BEFORE API call
|
|
181
|
+
validation = config[:_client].send(:validate_request, model, messages, max_tokens)
|
|
182
|
+
unless validation['allowed']
|
|
183
|
+
error_msg = validation['reason'] || 'Budget limit reached'
|
|
184
|
+
puts "[AgentBill Cost Guard] ❌ Request blocked: #{error_msg}" if config[:debug]
|
|
185
|
+
error = StandardError.new(error_msg)
|
|
186
|
+
error.define_singleton_method(:code) { 'BUDGET_EXCEEDED' }
|
|
187
|
+
raise error
|
|
188
|
+
end
|
|
60
189
|
|
|
61
|
-
|
|
62
|
-
|
|
190
|
+
# Phase 2: Execute AI call
|
|
191
|
+
start_time = Time.now
|
|
192
|
+
span = tracer.start_span('anthropic.message', {
|
|
193
|
+
'model' => model,
|
|
63
194
|
'provider' => 'anthropic'
|
|
64
195
|
})
|
|
65
196
|
|
|
66
197
|
begin
|
|
67
198
|
response = original_method.call(params)
|
|
68
|
-
|
|
69
199
|
latency = ((Time.now - start_time) * 1000).round
|
|
200
|
+
|
|
201
|
+
# Phase 3: Track actual usage
|
|
202
|
+
input_tokens = response.dig(:usage, :input_tokens) || 0
|
|
203
|
+
output_tokens = response.dig(:usage, :output_tokens) || 0
|
|
204
|
+
cost = config[:_client].send(:estimate_cost, model, input_tokens, output_tokens)
|
|
205
|
+
|
|
206
|
+
config[:_client].send(:track_usage, model, 'anthropic', input_tokens, output_tokens, latency, cost)
|
|
207
|
+
|
|
70
208
|
span.set_attributes({
|
|
71
|
-
'response.input_tokens' =>
|
|
72
|
-
'response.output_tokens' =>
|
|
209
|
+
'response.input_tokens' => input_tokens,
|
|
210
|
+
'response.output_tokens' => output_tokens,
|
|
73
211
|
'latency_ms' => latency
|
|
74
212
|
})
|
|
75
213
|
span.set_status(0)
|
|
76
214
|
|
|
215
|
+
puts "[AgentBill Cost Guard] ✓ Protected call completed: $#{format('%.4f', cost)}" if config[:debug]
|
|
77
216
|
response
|
|
78
217
|
rescue => e
|
|
79
218
|
span.set_status(1, e.message)
|
|
@@ -83,19 +222,49 @@ module AgentBill
|
|
|
83
222
|
end
|
|
84
223
|
end
|
|
85
224
|
|
|
225
|
+
# Store reference to self for helper methods
|
|
226
|
+
@config[:_client] = self
|
|
86
227
|
client
|
|
87
228
|
end
|
|
88
229
|
|
|
89
|
-
|
|
230
|
+
# Track a comprehensive signal with all 68 parameters
|
|
231
|
+
#
|
|
232
|
+
# Supports all parameters:
|
|
233
|
+
# - event_name (required)
|
|
234
|
+
# - data_source, timestamp
|
|
235
|
+
# - agent_external_id, customer_external_id, account_external_id, user_external_id,
|
|
236
|
+
# order_external_id, session_id, conversation_id, thread_id
|
|
237
|
+
# - model, provider, prompt_hash, prompt_sample, response_sample, function_name, tool_name
|
|
238
|
+
# - prompt_tokens, completion_tokens, total_tokens, streaming_tokens, cached_tokens, reasoning_tokens
|
|
239
|
+
# - latency_ms, time_to_first_token, time_to_action_ms, queue_time_ms, processing_time_ms
|
|
240
|
+
# - revenue, cost, conversion_value, revenue_source
|
|
241
|
+
# - experiment_id, experiment_group, variant_id, ab_test_name
|
|
242
|
+
# - conversion_type, conversion_step, funnel_stage, goal_achieved
|
|
243
|
+
# - feedback_score, user_satisfaction, error_type, error_message, retry_count, success_rate
|
|
244
|
+
# - tags, category, priority, severity, compliance_flag, data_classification
|
|
245
|
+
# - product_id, feature_flag, environment, deployment_version, region, tenant_id
|
|
246
|
+
# - parent_span_id, trace_id
|
|
247
|
+
# - custom_dimensions, metadata, data
|
|
248
|
+
#
|
|
249
|
+
# Example:
|
|
250
|
+
# agentbill.track_signal(
|
|
251
|
+
# event_name: "user_conversion",
|
|
252
|
+
# revenue: 99.99,
|
|
253
|
+
# customer_external_id: "cust_123",
|
|
254
|
+
# experiment_id: "exp_abc",
|
|
255
|
+
# conversion_type: "purchase",
|
|
256
|
+
# tags: ["checkout", "success"]
|
|
257
|
+
# )
|
|
258
|
+
def track_signal(**params)
|
|
259
|
+
raise ArgumentError, "event_name is required" unless params[:event_name]
|
|
260
|
+
|
|
90
261
|
uri = URI("#{@config[:base_url] || 'https://bgwyprqxtdreuutzpbgw.supabase.co'}/functions/v1/record-signals")
|
|
91
262
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
data: data
|
|
98
|
-
}
|
|
263
|
+
# Add timestamp if not provided
|
|
264
|
+
params[:timestamp] ||= Time.now.to_f
|
|
265
|
+
|
|
266
|
+
# Remove nil values
|
|
267
|
+
payload = params.reject { |_, v| v.nil? }
|
|
99
268
|
|
|
100
269
|
begin
|
|
101
270
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
@@ -109,12 +278,15 @@ module AgentBill
|
|
|
109
278
|
response = http.request(request)
|
|
110
279
|
|
|
111
280
|
if @config[:debug]
|
|
112
|
-
puts "[AgentBill] Signal tracked: #{event_name}
|
|
281
|
+
puts "[AgentBill] Signal tracked: #{params[:event_name]}"
|
|
113
282
|
end
|
|
283
|
+
|
|
284
|
+
response.code == '200'
|
|
114
285
|
rescue => e
|
|
115
286
|
if @config[:debug]
|
|
116
287
|
puts "[AgentBill] Failed to track signal: #{e.message}"
|
|
117
288
|
end
|
|
289
|
+
false
|
|
118
290
|
end
|
|
119
291
|
end
|
|
120
292
|
|
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: 1.0.
|
|
4
|
+
version: 1.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- AgentBill
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-10-
|
|
11
|
+
date: 2025-10-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -41,7 +41,7 @@ dependencies:
|
|
|
41
41
|
description: Automatically track and bill AI agent usage with zero-config instrumentation
|
|
42
42
|
for OpenAI, Anthropic, and more
|
|
43
43
|
email:
|
|
44
|
-
-
|
|
44
|
+
- dominic@agentbill.io
|
|
45
45
|
executables: []
|
|
46
46
|
extensions: []
|
|
47
47
|
extra_rdoc_files: []
|
|
@@ -50,6 +50,7 @@ files:
|
|
|
50
50
|
- ".github/ISSUE_TEMPLATE/feature_request.md"
|
|
51
51
|
- ".github/pull_request_template.md"
|
|
52
52
|
- ".github/workflows/ci.yml"
|
|
53
|
+
- ".github/workflows/publish.yml"
|
|
53
54
|
- ".gitignore"
|
|
54
55
|
- ".rspec"
|
|
55
56
|
- ".rubocop.yml"
|
|
@@ -63,7 +64,9 @@ files:
|
|
|
63
64
|
- SECURITY.md
|
|
64
65
|
- agentbill.gemspec
|
|
65
66
|
- examples/anthropic_basic.rb
|
|
67
|
+
- examples/manual_otel_tracking.rb
|
|
66
68
|
- examples/openai_basic.rb
|
|
69
|
+
- examples/zero_config.rb
|
|
67
70
|
- lib/agentbill.rb
|
|
68
71
|
- lib/agentbill/tracer.rb
|
|
69
72
|
- lib/agentbill/version.rb
|