inference_activity 1.0.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 +7 -0
- data/README.md +133 -0
- data/lib/inference_activity/version.rb +3 -0
- data/lib/inference_activity.rb +150 -0
- metadata +75 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 930b5b9c1533a9da594668af59131f20b9d670b621e6e1da59e9bbb1cab08617
|
|
4
|
+
data.tar.gz: 47f37d2e37c9bdf70d1cb5b906b0e6f143bca9be4fed5f8dc6169fc43e64c6e7
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 27ea85c2efcc589f767b4c23bb14038f52c1809b96e0488ebef08e5ad780c6d9a47fbcf394ba6f20f8ab28d1cc0f9a1dc411dcb34ba237dd0c9a28f22b3c6df0
|
|
7
|
+
data.tar.gz: 9763eca818ca551b36b9d94706d36ef7864d4c16ecaea00f85b1a503a758fc33ee28359a5dcd14e03fb7178f242dc92760d11c2f2cc7737e87ccbd5de69418c9
|
data/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Inference Activity for Ruby
|
|
2
|
+
|
|
3
|
+
A Ruby gem that provides Net::HTTP extensions for tracking inference activities on Heroku AI. This gem is the Ruby equivalent of `inference-activity-axios`.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'inference_activity'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install it yourself as:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
gem install inference_activity
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
The gem automatically extends Net::HTTP to track inference activities. Simply require it in your code:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
require 'inference_activity'
|
|
31
|
+
require 'net/http'
|
|
32
|
+
require 'json'
|
|
33
|
+
require 'uri'
|
|
34
|
+
|
|
35
|
+
# Set up your environment variables
|
|
36
|
+
ENV['INFERENCE_ACTIVITY_URL'] = 'your-activity-logging-endpoint'
|
|
37
|
+
ENV['INFERENCE_ACTIVITY_KEY'] = 'your-api-key'
|
|
38
|
+
|
|
39
|
+
# Make requests as usual with Net::HTTP
|
|
40
|
+
uri = URI('https://api.heroku.ai/v1/chat/completions')
|
|
41
|
+
request = Net::HTTP::Post.new(uri)
|
|
42
|
+
request['Authorization'] = "Bearer #{ENV['API_KEY']}"
|
|
43
|
+
request['Content-Type'] = 'application/json'
|
|
44
|
+
request.body = {
|
|
45
|
+
messages: [
|
|
46
|
+
{ role: 'user', content: 'Hello!' }
|
|
47
|
+
],
|
|
48
|
+
model: 'gpt-3.5-turbo'
|
|
49
|
+
}.to_json
|
|
50
|
+
|
|
51
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
|
52
|
+
http.request(request)
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The gem will automatically:
|
|
57
|
+
- Track the request/response
|
|
58
|
+
- Measure response time
|
|
59
|
+
- Redact sensitive information
|
|
60
|
+
- Send activity logs to your specified endpoint
|
|
61
|
+
|
|
62
|
+
## Features
|
|
63
|
+
|
|
64
|
+
- Automatically tracks inference activities for Heroku AI endpoints
|
|
65
|
+
- Redacts sensitive information from requests and responses
|
|
66
|
+
- Tracks response times and logs activity data
|
|
67
|
+
- Handles the following endpoints:
|
|
68
|
+
- `/v1/chat/completions`
|
|
69
|
+
- `/v1/embeddings`
|
|
70
|
+
- `/v1/images/generations`
|
|
71
|
+
|
|
72
|
+
## Environment Variables
|
|
73
|
+
|
|
74
|
+
- `INFERENCE_ACTIVITY_URL`: The endpoint where activity logs will be sent
|
|
75
|
+
- `INFERENCE_ACTIVITY_KEY`: The API key for authentication when sending activity logs
|
|
76
|
+
|
|
77
|
+
## Activity Data Format
|
|
78
|
+
|
|
79
|
+
The gem sends the following data structure to your activity logging endpoint:
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
{
|
|
83
|
+
timestamp: 1234567890123, # Unix timestamp in milliseconds
|
|
84
|
+
response_time: 500, # Response time in milliseconds
|
|
85
|
+
status_code: 200, # HTTP status code
|
|
86
|
+
status_message: "OK", # HTTP status message
|
|
87
|
+
request: {
|
|
88
|
+
method: "POST",
|
|
89
|
+
url: "https://api.heroku.ai/v1/chat/completions",
|
|
90
|
+
params: {},
|
|
91
|
+
body: {
|
|
92
|
+
messages: "[REDACTED]",
|
|
93
|
+
model: "gpt-3.5-turbo"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
response: {
|
|
97
|
+
headers: {
|
|
98
|
+
"content-type" => "application/json",
|
|
99
|
+
# ...
|
|
100
|
+
},
|
|
101
|
+
data: {
|
|
102
|
+
choices: [{
|
|
103
|
+
message: {
|
|
104
|
+
content: "[REDACTED]",
|
|
105
|
+
role: "assistant"
|
|
106
|
+
}
|
|
107
|
+
}],
|
|
108
|
+
# ...
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Differences from inference-activity-axios
|
|
115
|
+
|
|
116
|
+
This gem provides the same functionality as `inference-activity-axios` but is adapted for Ruby's Net::HTTP:
|
|
117
|
+
|
|
118
|
+
1. Uses Ruby's Net::HTTP extension system instead of Axios interceptors
|
|
119
|
+
2. Automatically applies to all Net::HTTP requests in the application
|
|
120
|
+
3. Uses Ruby's Time class for timestamps
|
|
121
|
+
4. Maintains consistent millisecond-precision timestamps for compatibility
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
After checking out the repo, run `bundle install` to install dependencies. Then, run `rake spec` to run the tests.
|
|
126
|
+
|
|
127
|
+
## Contributing
|
|
128
|
+
|
|
129
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/add-co/inference-activity-ruby.
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
The gem is available as open source under the terms of the MIT License.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'uri'
|
|
4
|
+
require 'time'
|
|
5
|
+
|
|
6
|
+
module InferenceActivity
|
|
7
|
+
class << self
|
|
8
|
+
def wrap_request(request, &block)
|
|
9
|
+
start_time = Time.now
|
|
10
|
+
|
|
11
|
+
# Execute the original request
|
|
12
|
+
response = yield
|
|
13
|
+
|
|
14
|
+
# Track activity if environment variables are set
|
|
15
|
+
track_activity(request, response, start_time) if tracking_enabled?
|
|
16
|
+
|
|
17
|
+
response
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def tracking_enabled?
|
|
23
|
+
!ENV['INFERENCE_ACTIVITY_URL'].nil? && !ENV['INFERENCE_ACTIVITY_KEY'].nil?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def track_activity(request, response, start_time)
|
|
27
|
+
duration = ((Time.now - start_time) * 1000).to_i # Convert to milliseconds
|
|
28
|
+
|
|
29
|
+
activity_data = {
|
|
30
|
+
timestamp: (Time.now.to_f * 1000).to_i, # Unix timestamp in milliseconds
|
|
31
|
+
response_time: duration,
|
|
32
|
+
status_code: response.code.to_i,
|
|
33
|
+
status_message: response.message,
|
|
34
|
+
request: redact_request(request),
|
|
35
|
+
response: redact_response(response)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
send_activity_data(activity_data)
|
|
39
|
+
rescue => e
|
|
40
|
+
warn "Failed to send inference activity: #{e.message}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def redact_request(request)
|
|
44
|
+
uri = URI(request.uri)
|
|
45
|
+
body = parse_request_body(request)
|
|
46
|
+
|
|
47
|
+
if body.is_a?(Hash)
|
|
48
|
+
if uri.path.end_with?('/v1/chat/completions') && body['messages']
|
|
49
|
+
body = body.merge('messages' => '[REDACTED]')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if uri.path.end_with?('/v1/embeddings') && body['input']
|
|
53
|
+
body = body.merge('input' => '[REDACTED]')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if uri.path.end_with?('/v1/images/generations')
|
|
57
|
+
body = body.merge('prompt' => '[REDACTED]') if body['prompt']
|
|
58
|
+
body = body.merge('negative_prompt' => '[REDACTED]') if body['negative_prompt']
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
{
|
|
63
|
+
method: request.method,
|
|
64
|
+
url: request.uri.to_s,
|
|
65
|
+
params: URI.decode_www_form(uri.query || '').to_h,
|
|
66
|
+
body: body
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def redact_response(response)
|
|
71
|
+
data = parse_response_body(response)
|
|
72
|
+
uri = URI(response.uri)
|
|
73
|
+
|
|
74
|
+
if data.is_a?(Hash)
|
|
75
|
+
if uri.path.end_with?('/v1/chat/completions') && data['choices']
|
|
76
|
+
data['choices'] = data['choices'].map do |choice|
|
|
77
|
+
if choice['message']
|
|
78
|
+
choice.merge('message' => choice['message'].merge('content' => '[REDACTED]'))
|
|
79
|
+
else
|
|
80
|
+
choice
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
if uri.path.end_with?('/v1/embeddings') && data['data']
|
|
86
|
+
data['data'] = data['data'].map do |item|
|
|
87
|
+
item.merge('embedding' => '[REDACTED]')
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
if uri.path.end_with?('/v1/images/generations') && data['data']
|
|
92
|
+
data['data'] = data['data'].map do |item|
|
|
93
|
+
redacted = item.dup
|
|
94
|
+
redacted['b64_json'] = '[REDACTED]' if item.key?('b64_json')
|
|
95
|
+
redacted['revised_prompt'] = '[REDACTED]' if item.key?('revised_prompt')
|
|
96
|
+
redacted
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
{
|
|
102
|
+
headers: response.each_header.to_h,
|
|
103
|
+
data: data
|
|
104
|
+
}
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def parse_request_body(request)
|
|
108
|
+
return {} unless request.body
|
|
109
|
+
|
|
110
|
+
JSON.parse(request.body)
|
|
111
|
+
rescue JSON::ParserError
|
|
112
|
+
request.body
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def parse_response_body(response)
|
|
116
|
+
JSON.parse(response.body)
|
|
117
|
+
rescue JSON::ParserError
|
|
118
|
+
response.body
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def send_activity_data(activity_data)
|
|
122
|
+
uri = URI(ENV['INFERENCE_ACTIVITY_URL'])
|
|
123
|
+
request = Net::HTTP::Post.new(uri)
|
|
124
|
+
request['Authorization'] = "Bearer #{ENV['INFERENCE_ACTIVITY_KEY']}"
|
|
125
|
+
request['Content-Type'] = 'application/json'
|
|
126
|
+
request.body = activity_data.to_json
|
|
127
|
+
|
|
128
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
|
129
|
+
http.request(request)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
module RequestExtensions
|
|
135
|
+
def self.included(base)
|
|
136
|
+
base.class_eval do
|
|
137
|
+
alias_method :original_exec, :exec
|
|
138
|
+
|
|
139
|
+
def exec(sock, ver, path)
|
|
140
|
+
InferenceActivity.wrap_request(self) do
|
|
141
|
+
original_exec(sock, ver, path)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Extend Net::HTTP::Request with our tracking functionality
|
|
150
|
+
Net::HTTPRequest.include(InferenceActivity::RequestExtensions)
|
metadata
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: inference_activity
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Add Co
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-03-04 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rake
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '13.0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '13.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rspec
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '3.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '3.0'
|
|
41
|
+
description: Provides request/response tracking and activity logging for Heroku AI
|
|
42
|
+
endpoints with sensitive data redaction
|
|
43
|
+
email:
|
|
44
|
+
- dev@add.co
|
|
45
|
+
executables: []
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- README.md
|
|
50
|
+
- lib/inference_activity.rb
|
|
51
|
+
- lib/inference_activity/version.rb
|
|
52
|
+
homepage: https://github.com/add-co/inference-activity-ruby
|
|
53
|
+
licenses:
|
|
54
|
+
- MIT
|
|
55
|
+
metadata: {}
|
|
56
|
+
post_install_message:
|
|
57
|
+
rdoc_options: []
|
|
58
|
+
require_paths:
|
|
59
|
+
- lib
|
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
61
|
+
requirements:
|
|
62
|
+
- - ">="
|
|
63
|
+
- !ruby/object:Gem::Version
|
|
64
|
+
version: 2.7.0
|
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0'
|
|
70
|
+
requirements: []
|
|
71
|
+
rubygems_version: 3.3.15
|
|
72
|
+
signing_key:
|
|
73
|
+
specification_version: 4
|
|
74
|
+
summary: Net::HTTP extensions for tracking inference activities on Heroku AI
|
|
75
|
+
test_files: []
|