tork-governance 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66f973f8bfa19b713628ce8bb3942b67fba1bbdf6a138c55ec56060ffb2e0c2e
4
- data.tar.gz: 4f9d8086d72580bac59d31397cb62d5ce21a3ca053f6b54ba9b4df69d73a410a
3
+ metadata.gz: 216554d503f29e742fd17648fe812add23b32f509c5af2a3df6620ff278e0c84
4
+ data.tar.gz: 3389e8d1f22910eed0a302963837a152bf541303690938d816ef7257b31be926
5
5
  SHA512:
6
- metadata.gz: fc1120abb85021b2e921f577925e469a6b4a6daa69389136ab96be1720e99f715977bc8041277b3931c0d109d19c2f107ea2c81ee0b4fe0af3e1e2ae0197552e
7
- data.tar.gz: 967c1f7abdfdf9274c994487921de2e314fef6dcdaf7849164d5b72a99191138e099e17967b8b6085b03ec6882b29aaa676f11e8cc56469aa0e24d35008c3c1e
6
+ metadata.gz: 8943faa1f06adf99915b6d3d2080a4890b9b42d399ad3e2464cd3e5a4caf90e58c88b730a59491caefe8672dcacc9fd54ed7afb8a5d38b8c3631a99da72f1942
7
+ data.tar.gz: 284b06a25065b00b0eddb0e581f376d15a097d58adfddeefab49eb2e579cc63593e85d2d991121e13ee89d461f564a48d2d1f147c59a67b75f6e8d55fa221821
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Tork Network Pty Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,26 +1,19 @@
1
- # Tork AI Governance SDK for Ruby
1
+ # Tork Governance Ruby SDK
2
2
 
3
- Official Ruby SDK for the [Tork AI Governance Platform](https://tork.network). Provides comprehensive tools for AI safety, content moderation, PII detection, policy enforcement, and compliance monitoring for LLM applications.
3
+ On-device AI governance with PII detection, redaction, and cryptographic receipts for Ruby applications.
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/tork-governance.svg)](https://badge.fury.io/rb/tork-governance)
6
- [![Build Status](https://github.com/torkjacobs/tork-ruby-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/torkjacobs/tork-ruby-sdk/actions)
7
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
7
 
9
8
  ## Installation
10
9
 
11
- Add this line to your application's Gemfile:
10
+ Add to your Gemfile:
12
11
 
13
12
  ```ruby
14
13
  gem 'tork-governance'
15
14
  ```
16
15
 
17
- And then execute:
18
-
19
- ```bash
20
- bundle install
21
- ```
22
-
23
- Or install it yourself as:
16
+ Or install directly:
24
17
 
25
18
  ```bash
26
19
  gem install tork-governance
@@ -29,397 +22,114 @@ gem install tork-governance
29
22
  ## Quick Start
30
23
 
31
24
  ```ruby
32
- require 'tork'
25
+ require 'tork_governance'
33
26
 
34
- # Configure with your API key
35
- Tork.configure do |config|
36
- config.api_key = 'tork_your_api_key'
37
- end
38
-
39
- # Evaluate content
40
- result = Tork.evaluate(prompt: "What is the capital of France?")
41
- puts result['data']['passed'] # => true
42
- ```
27
+ tork = TorkGovernance::Client.new
43
28
 
44
- ## Configuration
29
+ # Detect and redact PII
30
+ result = tork.govern("My SSN is 123-45-6789 and email is john@example.com")
45
31
 
46
- ### Global Configuration
47
-
48
- ```ruby
49
- Tork.configure do |config|
50
- config.api_key = 'tork_your_api_key'
51
- config.base_url = 'https://api.tork.network/v1' # Default
52
- config.timeout = 30 # Request timeout in seconds
53
- config.max_retries = 3 # Max retry attempts
54
- config.retry_base_delay = 0.5 # Base delay for exponential backoff
55
- config.raise_on_rate_limit = true # Raise exception on rate limit
56
- config.logger = Logger.new(STDOUT) # Enable logging
57
- end
32
+ puts result.output # "My SSN is [SSN_REDACTED] and email is [EMAIL_REDACTED]"
33
+ puts result.pii.types # ['ssn', 'email']
34
+ puts result.receipt.id # Cryptographic receipt ID
58
35
  ```
59
36
 
60
- ### Environment Variable
37
+ ## Supported Frameworks (2 Adapters)
61
38
 
62
- You can also set the API key via environment variable:
39
+ ### Web Frameworks
40
+ - **Rails** - Middleware and controller integration
41
+ - **Grape** - API middleware and helpers
63
42
 
64
- ```bash
65
- export TORK_API_KEY=tork_your_api_key
66
- ```
43
+ ## Framework Examples
67
44
 
68
- ### Per-Client Configuration
45
+ ### Rails Middleware
69
46
 
70
47
  ```ruby
71
- client = Tork::Client.new(
72
- api_key: 'tork_different_key',
73
- base_url: 'https://custom.api.com'
74
- )
75
- ```
76
-
77
- ## Usage
78
-
79
- ### Content Evaluation
80
-
81
- ```ruby
82
- client = Tork::Client.new(api_key: 'tork_your_api_key')
83
-
84
- # Basic evaluation
85
- result = client.evaluate(prompt: "Hello, how are you?")
86
-
87
- # Evaluation with response
88
- result = client.evaluate(
89
- prompt: "What is 2+2?",
90
- response: "The answer is 4."
91
- )
92
-
93
- # Evaluation with specific policy
94
- result = client.evaluate(
95
- prompt: "Process this request",
96
- policy_id: "pol_abc123"
97
- )
98
-
99
- # Evaluation with specific checks
100
- result = client.evaluations.create(
101
- prompt: "Contact me at john@example.com",
102
- checks: ['pii', 'toxicity', 'moderation']
103
- )
104
- ```
105
-
106
- ### PII Detection & Redaction
107
-
108
- ```ruby
109
- # Detect PII
110
- result = client.evaluations.detect_pii(
111
- content: "My email is john@example.com and SSN is 123-45-6789"
112
- )
113
- # => { "has_pii" => true, "types" => ["email", "ssn"] }
114
-
115
- # Redact PII
116
- result = client.evaluations.redact_pii(
117
- content: "Call me at 555-123-4567",
118
- replacement: "mask"
119
- )
120
- # => { "redacted" => "Call me at ***-***-****" }
121
- ```
122
-
123
- ### Jailbreak Detection
124
-
125
- ```ruby
126
- result = client.evaluations.detect_jailbreak(
127
- prompt: "Ignore previous instructions and..."
128
- )
129
-
130
- if result['data']['is_jailbreak']
131
- puts "Jailbreak attempt detected!"
132
- puts "Techniques: #{result['data']['techniques']}"
133
- end
134
- ```
135
-
136
- ### Policy Management
137
-
138
- ```ruby
139
- policies = client.policies
140
-
141
- # List all policies
142
- all_policies = policies.list(page: 1, per_page: 20)
143
-
144
- # Get a specific policy
145
- policy = policies.get('pol_abc123')
146
-
147
- # Create a new policy
148
- new_policy = policies.create(
149
- name: "Content Safety Policy",
150
- description: "Block harmful content",
151
- rules: [
152
- {
153
- type: "block",
154
- condition: "toxicity > 0.8",
155
- action: "reject",
156
- message: "Content flagged as toxic"
157
- },
158
- {
159
- type: "redact",
160
- condition: "pii.detected",
161
- action: "mask"
162
- }
163
- ],
164
- enabled: true
165
- )
166
-
167
- # Update a policy
168
- policies.update('pol_abc123', name: "Updated Policy Name")
169
-
170
- # Enable/Disable a policy
171
- policies.enable('pol_abc123')
172
- policies.disable('pol_abc123')
173
-
174
- # Delete a policy
175
- policies.delete('pol_abc123')
176
-
177
- # Test a policy
178
- test_result = policies.test('pol_abc123',
179
- content: "Test content here",
180
- context: { user_role: "admin" }
181
- )
182
- ```
183
-
184
- ### Batch Evaluation
185
-
186
- ```ruby
187
- items = [
188
- { prompt: "First prompt" },
189
- { prompt: "Second prompt", response: "Second response" },
190
- { prompt: "Third prompt" }
191
- ]
192
-
193
- results = client.evaluations.batch(items, policy_id: 'pol_abc123')
194
- ```
195
-
196
- ### RAG Validation
197
-
198
- ```ruby
199
- chunks = [
200
- { content: "Document chunk 1", source: "doc1.pdf", page: 1 },
201
- { content: "Document chunk 2", source: "doc2.pdf", page: 3 }
202
- ]
203
-
204
- result = client.evaluations.validate_rag(
205
- chunks: chunks,
206
- query: "What is the company policy?"
207
- )
208
- ```
209
-
210
- ### Metrics & Analytics
211
-
212
- ```ruby
213
- metrics = client.metrics
214
-
215
- # Get Torking X score for an evaluation
216
- score = metrics.torking_x(evaluation_id: 'eval_abc123')
217
-
218
- # Get usage statistics
219
- usage = metrics.usage(period: 'month')
220
-
221
- # Get policy performance
222
- performance = metrics.policy_performance(policy_id: 'pol_abc123')
223
-
224
- # Get violation statistics
225
- violations = metrics.violations(period: 'week', group_by: 'type')
226
-
227
- # Get dashboard summary
228
- dashboard = metrics.dashboard
229
-
230
- # Get latency metrics
231
- latency = metrics.latency(period: 'day', percentiles: [50, 95, 99])
232
-
233
- # Export metrics
234
- export = metrics.export(
235
- type: 'usage',
236
- start_date: '2024-01-01',
237
- end_date: '2024-01-31',
238
- format: 'csv'
239
- )
240
- ```
241
-
242
- ## Error Handling
243
-
244
- ```ruby
245
- begin
246
- result = client.evaluate(prompt: "Test content")
247
- rescue Tork::AuthenticationError => e
248
- puts "Invalid API key: #{e.message}"
249
- rescue Tork::RateLimitError => e
250
- puts "Rate limited. Retry after #{e.retry_after} seconds"
251
- rescue Tork::ValidationError => e
252
- puts "Validation failed: #{e.message}"
253
- puts "Details: #{e.details}"
254
- rescue Tork::PolicyViolationError => e
255
- puts "Policy violation: #{e.message}"
256
- puts "Violations: #{e.violations}"
257
- rescue Tork::NotFoundError => e
258
- puts "Resource not found: #{e.message}"
259
- rescue Tork::ServerError => e
260
- puts "Server error: #{e.message}"
261
- rescue Tork::TimeoutError => e
262
- puts "Request timed out"
263
- rescue Tork::ConnectionError => e
264
- puts "Connection failed"
265
- rescue Tork::Error => e
266
- puts "Tork error: #{e.message}"
267
- end
268
- ```
269
-
270
- ## Rails Integration
271
-
272
- ### Initializer
273
-
274
- Create `config/initializers/tork.rb`:
275
-
276
- ```ruby
277
- Tork.configure do |config|
278
- config.api_key = Rails.application.credentials.tork_api_key
279
- config.logger = Rails.logger
280
- config.timeout = 30
48
+ # config/application.rb
49
+ module MyApp
50
+ class Application < Rails::Application
51
+ config.middleware.use TorkGovernance::Middleware::Rails,
52
+ protected_paths: ['/api/'],
53
+ skip_paths: ['/api/health']
54
+ end
281
55
  end
282
56
  ```
283
57
 
284
- ### Controller Example
285
-
286
58
  ```ruby
287
- class MessagesController < ApplicationController
59
+ # In controllers
60
+ class ChatController < ApplicationController
288
61
  def create
289
- result = Tork.evaluate(
290
- prompt: params[:content],
291
- policy_id: current_user.organization.policy_id
292
- )
293
-
294
- if result['data']['passed']
295
- @message = Message.create!(content: params[:content])
296
- render json: @message
297
- else
298
- render json: {
299
- error: 'Content blocked',
300
- violations: result['data']['violations']
301
- }, status: :unprocessable_entity
302
- end
303
- rescue Tork::RateLimitError => e
304
- render json: { error: 'Rate limited' }, status: :too_many_requests
62
+ tork_result = request.env['tork.result']
63
+ render json: { status: 'ok', receipt_id: tork_result&.receipt&.id }
305
64
  end
306
65
  end
307
66
  ```
308
67
 
309
- ### Background Job Example
68
+ ### Grape API Middleware
310
69
 
311
70
  ```ruby
312
- class ContentModerationJob < ApplicationJob
313
- queue_as :default
71
+ require 'tork_governance/middleware/grape'
314
72
 
315
- def perform(message_id)
316
- message = Message.find(message_id)
73
+ class API < Grape::API
74
+ use TorkGovernance::Middleware::Grape,
75
+ protected_paths: ['/api/'],
76
+ skip_paths: ['/api/health']
317
77
 
318
- result = Tork.evaluate(
319
- prompt: message.content,
320
- checks: ['toxicity', 'pii']
321
- )
78
+ helpers TorkGovernance::Middleware::GrapeHelpers
322
79
 
323
- if result['data']['violations'].any?
324
- message.update!(
325
- flagged: true,
326
- moderation_result: result['data']
327
- )
80
+ post '/chat' do
81
+ result = tork_result
82
+ receipt_id = tork_receipt_id
328
83
 
329
- NotificationService.notify_moderators(message)
84
+ if tork_blocked?
85
+ error!({ error: 'Content blocked' }, 403)
330
86
  end
87
+
88
+ { status: 'ok', receipt_id: receipt_id }
331
89
  end
332
90
  end
333
91
  ```
334
92
 
335
- ## Logging
336
-
337
- Enable detailed logging for debugging:
93
+ ### Grape Helper Methods
338
94
 
339
95
  ```ruby
340
- require 'logger'
341
-
342
- Tork.configure do |config|
343
- config.api_key = 'tork_your_api_key'
344
- config.logger = Logger.new(STDOUT)
345
- end
346
- ```
347
-
348
- ## Retry Behavior
96
+ helpers TorkGovernance::Middleware::GrapeHelpers
349
97
 
350
- The SDK automatically retries failed requests with exponential backoff:
351
-
352
- - **Retryable status codes**: 408, 500, 502, 503, 504
353
- - **Default max retries**: 3
354
- - **Default base delay**: 0.5 seconds
355
- - **Backoff factor**: 2x
356
- - **Jitter**: 50% randomness
357
-
358
- Customize retry behavior:
359
-
360
- ```ruby
361
- Tork.configure do |config|
362
- config.max_retries = 5
363
- config.retry_base_delay = 1.0
364
- config.retry_max_delay = 60.0
365
- end
98
+ # Available helpers:
99
+ tork_result # Get full governance result
100
+ tork_receipt_id # Get receipt ID
101
+ tork_redacted_content # Get redacted content
102
+ tork_blocked? # Check if request was blocked
103
+ tork_redacted? # Check if content was redacted
104
+ require_tork_governance! # Raises 403 if blocked
366
105
  ```
367
106
 
368
- ## Thread Safety
369
-
370
- The SDK is thread-safe. Each `Tork::Client` instance maintains its own connection pool.
107
+ ## Configuration
371
108
 
372
109
  ```ruby
373
- # Shared client (thread-safe)
374
- client = Tork::Client.new(api_key: 'tork_your_api_key')
375
-
376
- threads = 10.times.map do |i|
377
- Thread.new do
378
- client.evaluate(prompt: "Thread #{i} content")
379
- end
380
- end
381
-
382
- threads.each(&:join)
110
+ TorkGovernance.configure(
111
+ api_key: ENV['TORK_API_KEY'],
112
+ policy_version: '1.0.0',
113
+ default_action: :redact
114
+ )
383
115
  ```
384
116
 
385
- ## Development
386
-
387
- After checking out the repo:
388
-
389
- ```bash
390
- # Install dependencies
391
- bundle install
392
-
393
- # Run tests
394
- bundle exec rspec
117
+ ## PII Detection
395
118
 
396
- # Run linter
397
- bundle exec rubocop
119
+ Detects 50+ PII types including:
398
120
 
399
- # Generate documentation
400
- bundle exec rake doc
121
+ | Category | Types |
122
+ |----------|-------|
123
+ | **US** | SSN, EIN, ITIN, Passport, Driver's License |
124
+ | **Australia** | TFN, ABN, ACN, Medicare |
125
+ | **Financial** | Credit Card, Bank Account, SWIFT/BIC |
126
+ | **Universal** | Email, IP Address, URL, Phone, DOB |
401
127
 
402
- # Build the gem
403
- bundle exec rake build
404
-
405
- # Install locally
406
- bundle exec rake install
407
- ```
128
+ ## Documentation
408
129
 
409
- ## Contributing
410
-
411
- 1. Fork the repository
412
- 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
413
- 3. Commit your changes (`git commit -am 'Add amazing feature'`)
414
- 4. Push to the branch (`git push origin feature/amazing-feature`)
415
- 5. Open a Pull Request
130
+ - [Full Documentation](https://docs.tork.network)
131
+ - [API Reference](https://docs.tork.network/api/ruby)
416
132
 
417
133
  ## License
418
134
 
419
- This gem is available as open source under the [MIT License](https://opensource.org/licenses/MIT).
420
-
421
- ## Support
422
-
423
- - **Documentation**: [docs.tork.network](https://docs.tork.network)
424
- - **Email**: support@tork.network
425
- - **Issues**: [GitHub Issues](https://github.com/torkjacobs/tork-ruby-sdk/issues)
135
+ MIT License - see [LICENSE](LICENSE) for details.
data/Rakefile CHANGED
@@ -1,38 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
3
  require "rspec/core/rake_task"
5
- require "rubocop/rake_task"
6
4
 
7
5
  RSpec::Core::RakeTask.new(:spec)
8
- RuboCop::RakeTask.new
9
6
 
10
- task default: %i[spec rubocop]
11
-
12
- desc "Run tests with coverage"
13
- task :coverage do
14
- ENV["COVERAGE"] = "true"
15
- Rake::Task[:spec].invoke
16
- end
17
-
18
- desc "Generate documentation"
19
- task :doc do
20
- sh "yard doc --output-dir doc lib/**/*.rb"
21
- end
22
-
23
- desc "Open documentation in browser"
24
- task :doc_server do
25
- sh "yard server --reload"
26
- end
27
-
28
- desc "Build and install gem locally"
29
- task :local_install do
30
- sh "gem build tork-governance.gemspec"
31
- sh "gem install tork-governance-#{Tork::VERSION}.gem"
32
- end
33
-
34
- desc "Release gem to RubyGems"
35
- task :publish do
36
- sh "gem build tork-governance.gemspec"
37
- sh "gem push tork-governance-#{Tork::VERSION}.gem"
38
- end
7
+ task default: :spec
data/lib/tork/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tork
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TorkGovernance
4
+ # Governance result
5
+ class GovernResult
6
+ attr_reader :action, :output, :pii, :receipt
7
+
8
+ def initialize(action:, output:, pii:, receipt:)
9
+ @action = action
10
+ @output = output
11
+ @pii = pii
12
+ @receipt = receipt
13
+ end
14
+
15
+ def allowed?
16
+ action == ACTIONS[:allow]
17
+ end
18
+
19
+ def denied?
20
+ action == ACTIONS[:deny]
21
+ end
22
+
23
+ def redacted?
24
+ action == ACTIONS[:redact]
25
+ end
26
+
27
+ def to_h
28
+ {
29
+ action: action,
30
+ output: output,
31
+ pii: {
32
+ has_pii: pii.has_pii?,
33
+ types: pii.types,
34
+ count: pii.count
35
+ },
36
+ receipt: receipt.to_h
37
+ }
38
+ end
39
+ end
40
+
41
+ # Main Tork governance client
42
+ class Client
43
+ attr_reader :api_key, :policy_version, :default_action, :stats
44
+
45
+ def initialize(api_key: nil, policy_version: "1.0.0", default_action: ACTIONS[:redact])
46
+ @api_key = api_key
47
+ @policy_version = policy_version
48
+ @default_action = default_action
49
+ @stats = {
50
+ total_calls: 0,
51
+ total_pii_detected: 0,
52
+ total_processing_ns: 0,
53
+ action_counts: Hash.new(0)
54
+ }
55
+ end
56
+
57
+ # Apply governance to content
58
+ #
59
+ # @param input [String] the content to govern
60
+ # @return [GovernResult] the governance result
61
+ #
62
+ # @example
63
+ # client = TorkGovernance::Client.new
64
+ # result = client.govern("My email is test@example.com")
65
+ # puts result.output # "My email is [EMAIL_REDACTED]"
66
+ # puts result.receipt.id # "rcpt_..."
67
+ def govern(input)
68
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
69
+
70
+ # Detect PII
71
+ pii = PIIDetector.detect(input)
72
+
73
+ # Determine action and output
74
+ if pii.has_pii?
75
+ action = default_action
76
+ output = action == ACTIONS[:redact] ? pii.redacted_text : input
77
+ else
78
+ action = ACTIONS[:allow]
79
+ output = input
80
+ end
81
+
82
+ processing_time_ns = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) - start_time
83
+
84
+ # Generate receipt
85
+ receipt = Receipt.generate(
86
+ input: input,
87
+ output: output,
88
+ action: action,
89
+ pii_types: pii.types,
90
+ pii_count: pii.count,
91
+ policy_version: policy_version,
92
+ processing_time_ns: processing_time_ns
93
+ )
94
+
95
+ # Update stats
96
+ @stats[:total_calls] += 1
97
+ @stats[:total_pii_detected] += 1 if pii.has_pii?
98
+ @stats[:total_processing_ns] += processing_time_ns
99
+ @stats[:action_counts][action] += 1
100
+
101
+ GovernResult.new(
102
+ action: action,
103
+ output: output,
104
+ pii: pii,
105
+ receipt: receipt
106
+ )
107
+ end
108
+
109
+ # Reset statistics
110
+ def reset_stats
111
+ @stats = {
112
+ total_calls: 0,
113
+ total_pii_detected: 0,
114
+ total_processing_ns: 0,
115
+ action_counts: Hash.new(0)
116
+ }
117
+ end
118
+ end
119
+ end