tracebook 0.1.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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +10 -0
  3. data/CHANGELOG.md +43 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +881 -0
  6. data/Rakefile +21 -0
  7. data/app/assets/images/tracebook/.keep +0 -0
  8. data/app/assets/javascripts/tracebook/application.js +88 -0
  9. data/app/assets/stylesheets/tracebook/application.css +173 -0
  10. data/app/controllers/concerns/.keep +0 -0
  11. data/app/controllers/tracebook/application_controller.rb +4 -0
  12. data/app/controllers/tracebook/exports_controller.rb +25 -0
  13. data/app/controllers/tracebook/interactions_controller.rb +71 -0
  14. data/app/helpers/tracebook/application_helper.rb +4 -0
  15. data/app/helpers/tracebook/interactions_helper.rb +35 -0
  16. data/app/jobs/tracebook/application_job.rb +5 -0
  17. data/app/jobs/tracebook/daily_rollups_job.rb +100 -0
  18. data/app/jobs/tracebook/export_job.rb +162 -0
  19. data/app/jobs/tracebook/persist_interaction_job.rb +160 -0
  20. data/app/mailers/tracebook/application_mailer.rb +6 -0
  21. data/app/models/concerns/.keep +0 -0
  22. data/app/models/tracebook/application_record.rb +5 -0
  23. data/app/models/tracebook/interaction.rb +100 -0
  24. data/app/models/tracebook/pricing_rule.rb +84 -0
  25. data/app/models/tracebook/redaction_rule.rb +81 -0
  26. data/app/models/tracebook/rollup_daily.rb +73 -0
  27. data/app/views/layouts/tracebook/application.html.erb +18 -0
  28. data/app/views/tracebook/interactions/index.html.erb +105 -0
  29. data/app/views/tracebook/interactions/show.html.erb +44 -0
  30. data/config/routes.rb +8 -0
  31. data/db/migrate/20241112000100_create_tracebook_interactions.rb +55 -0
  32. data/db/migrate/20241112000200_create_tracebook_rollups_dailies.rb +24 -0
  33. data/db/migrate/20241112000300_create_tracebook_pricing_rules.rb +21 -0
  34. data/db/migrate/20241112000400_create_tracebook_redaction_rules.rb +19 -0
  35. data/lib/tasks/tracebook_tasks.rake +4 -0
  36. data/lib/tasks/yard.rake +29 -0
  37. data/lib/tracebook/adapters/active_agent.rb +82 -0
  38. data/lib/tracebook/adapters/ruby_llm.rb +97 -0
  39. data/lib/tracebook/adapters.rb +6 -0
  40. data/lib/tracebook/config.rb +130 -0
  41. data/lib/tracebook/engine.rb +5 -0
  42. data/lib/tracebook/errors.rb +9 -0
  43. data/lib/tracebook/mappers/anthropic.rb +59 -0
  44. data/lib/tracebook/mappers/base.rb +38 -0
  45. data/lib/tracebook/mappers/ollama.rb +49 -0
  46. data/lib/tracebook/mappers/openai.rb +75 -0
  47. data/lib/tracebook/mappers.rb +283 -0
  48. data/lib/tracebook/normalized_interaction.rb +86 -0
  49. data/lib/tracebook/pricing/calculator.rb +39 -0
  50. data/lib/tracebook/pricing.rb +5 -0
  51. data/lib/tracebook/redaction_pipeline.rb +88 -0
  52. data/lib/tracebook/redactors/base.rb +29 -0
  53. data/lib/tracebook/redactors/card_pan.rb +15 -0
  54. data/lib/tracebook/redactors/email.rb +15 -0
  55. data/lib/tracebook/redactors/phone.rb +15 -0
  56. data/lib/tracebook/redactors.rb +8 -0
  57. data/lib/tracebook/result.rb +53 -0
  58. data/lib/tracebook/version.rb +3 -0
  59. data/lib/tracebook.rb +201 -0
  60. metadata +164 -0
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tracebook
4
+ # Result object returned by {TraceBook.record!}.
5
+ #
6
+ # Contains either the persisted interaction (when sync) or success/error information.
7
+ #
8
+ # @example Successful async recording
9
+ # result = TraceBook.record!(provider: "openai", model: "gpt-4o", ...)
10
+ # result.success? # => true
11
+ # result.interaction # => nil (async - not available yet)
12
+ #
13
+ # @example Successful sync recording
14
+ # TraceBook.configure { |c| c.persist_async = false }
15
+ # result = TraceBook.record!(provider: "openai", model: "gpt-4o", ...)
16
+ # result.success? # => true
17
+ # result.interaction # => #<TraceBook::Interaction id: 123>
18
+ #
19
+ # @example Failed recording
20
+ # result = TraceBook.record!(provider: nil, model: nil) # Invalid
21
+ # result.success? # => false
22
+ # result.error # => #<KeyError: key not found: :provider>
23
+ class Result
24
+ # @return [TraceBook::Interaction, nil] The persisted interaction (sync mode only)
25
+ attr_reader :interaction
26
+
27
+ # @return [Exception, nil] The error that occurred during recording
28
+ attr_reader :error
29
+
30
+ # @return [String, nil] Idempotency key for deduplication
31
+ attr_reader :idempotency_key
32
+
33
+ # Creates a new Result.
34
+ #
35
+ # @param interaction [TraceBook::Interaction, nil] Persisted interaction
36
+ # @param error [Exception, nil] Error that occurred
37
+ # @param idempotency_key [String, nil] Deduplication key
38
+ def initialize(interaction: nil, error: nil, idempotency_key: nil)
39
+ @interaction = interaction
40
+ @error = error
41
+ @idempotency_key = idempotency_key
42
+ end
43
+
44
+ # Returns true if recording succeeded (no error).
45
+ #
46
+ # @return [Boolean] true when no error occurred
47
+ def success?
48
+ error.nil?
49
+ end
50
+ end
51
+ end
52
+
53
+ TraceBook = Tracebook unless defined?(TraceBook)
@@ -0,0 +1,3 @@
1
+ module Tracebook
2
+ VERSION = "0.1.0"
3
+ end
data/lib/tracebook.rb ADDED
@@ -0,0 +1,201 @@
1
+ require "tracebook/version"
2
+ require "tracebook/engine"
3
+ require "tracebook/errors"
4
+ require "tracebook/redactors"
5
+ require "tracebook/mappers"
6
+ require "tracebook/config"
7
+ require "tracebook/result"
8
+ require "tracebook/normalized_interaction"
9
+ require "tracebook/redaction_pipeline"
10
+ require "tracebook/pricing"
11
+ require "tracebook/adapters"
12
+
13
+ # TraceBook is a Rails engine for capturing, storing, and reviewing LLM interactions.
14
+ #
15
+ # It provides:
16
+ # - Automatic redaction of PII from request/response payloads
17
+ # - Encrypted storage of sensitive data using ActiveRecord::Encryption
18
+ # - Cost tracking based on token usage and configurable pricing rules
19
+ # - Review workflow (approve/flag/reject) with audit trail
20
+ # - Hotwire-powered dashboard UI with filtering and export
21
+ # - Built-in adapters for OpenAI, Anthropic, Ollama
22
+ # - Support for hierarchical agent sessions (parent-child relationships)
23
+ #
24
+ # @example Basic configuration
25
+ # TraceBook.configure do |config|
26
+ # config.authorize = ->(user, action, resource) { user&.admin? }
27
+ # config.project_name = "My App"
28
+ # config.persist_async = Rails.env.production?
29
+ # end
30
+ #
31
+ # @example Recording an interaction
32
+ # TraceBook.record!(
33
+ # provider: "openai",
34
+ # model: "gpt-4o",
35
+ # request_payload: { messages: messages },
36
+ # response_payload: response,
37
+ # input_tokens: 100,
38
+ # output_tokens: 50,
39
+ # user: current_user,
40
+ # tags: ["production", "support"]
41
+ # )
42
+ #
43
+ # @see https://github.com/dpaluy/tracebook README for full documentation
44
+ module Tracebook
45
+ class << self
46
+ # Returns the current configuration instance.
47
+ #
48
+ # @return [Tracebook::Config] the configuration object
49
+ def config
50
+ @config ||= Config.new
51
+ end
52
+
53
+ # Configures TraceBook with a block.
54
+ #
55
+ # Configuration is frozen after the block executes. Call {#reset_configuration!}
56
+ # in tests to reset.
57
+ #
58
+ # @yield [config] Yields the config object for modification
59
+ # @yieldparam config [Tracebook::Config] the configuration object
60
+ # @return [Tracebook::Config] the finalized configuration
61
+ # @raise [ConfigurationError] if configuration is already finalized
62
+ #
63
+ # @example
64
+ # TraceBook.configure do |config|
65
+ # config.authorize = ->(user, action, resource) { user&.admin? }
66
+ # config.persist_async = true
67
+ # config.project_name = "Support App"
68
+ # end
69
+ def configure
70
+ ensure_configurable!
71
+
72
+ yield(config)
73
+ finalize_configuration!
74
+ config
75
+ end
76
+
77
+ # Resets configuration to a clean state.
78
+ #
79
+ # Used in tests to start with fresh configuration between test cases.
80
+ #
81
+ # @return [void]
82
+ #
83
+ # @example In test setup
84
+ # setup do
85
+ # TraceBook.reset_configuration!
86
+ # TraceBook.configure do |config|
87
+ # config.authorize = ->(*) { true }
88
+ # end
89
+ # end
90
+ def reset_configuration!
91
+ @config = Config.new
92
+ @configuration_finalized = false
93
+ end
94
+
95
+ # Records an LLM interaction.
96
+ #
97
+ # When `config.persist_async` is true, the interaction is enqueued via
98
+ # {PersistInteractionJob}. Otherwise, it's persisted inline.
99
+ #
100
+ # @param attributes [Hash] Interaction attributes
101
+ # @option attributes [String] :provider Provider name (e.g., "openai", "anthropic") **required**
102
+ # @option attributes [String] :model Model identifier (e.g., "gpt-4o", "claude-3-5-sonnet") **required**
103
+ # @option attributes [String, nil] :project Project name for filtering
104
+ # @option attributes [Hash, nil] :request_payload Full request sent to provider (will be encrypted)
105
+ # @option attributes [Hash, nil] :response_payload Full response from provider (will be encrypted)
106
+ # @option attributes [String, nil] :request_text Human-readable request summary
107
+ # @option attributes [String, nil] :response_text Human-readable response summary
108
+ # @option attributes [Integer, nil] :input_tokens Prompt token count
109
+ # @option attributes [Integer, nil] :output_tokens Completion token count
110
+ # @option attributes [Integer, nil] :latency_ms Request duration in milliseconds
111
+ # @option attributes [Symbol, String] :status :success, :error, or :canceled (default: :success)
112
+ # @option attributes [String, nil] :error_class Exception class name on failure
113
+ # @option attributes [String, nil] :error_message Exception message on failure
114
+ # @option attributes [Array<String>] :tags Labels for filtering (e.g., ["prod", "urgent"])
115
+ # @option attributes [Hash] :metadata Custom metadata (e.g., { ticket_id: 123 })
116
+ # @option attributes [ActiveRecord::Base, nil] :user Associated user (polymorphic)
117
+ # @option attributes [String, nil] :session_id Session identifier for grouping related calls
118
+ # @option attributes [Integer, nil] :parent_id Parent interaction ID for hierarchical chains
119
+ # @option attributes [String, nil] :idempotency_key Key for deduplication
120
+ #
121
+ # @return [Tracebook::Result] Result object with success/error information
122
+ #
123
+ # @example Recording a successful completion
124
+ # result = TraceBook.record!(
125
+ # provider: "openai",
126
+ # model: "gpt-4o-mini",
127
+ # request_payload: { messages: [{ role: "user", content: "Hello" }] },
128
+ # response_payload: { choices: [{ message: { content: "Hi!" } }] },
129
+ # input_tokens: 10,
130
+ # output_tokens: 5,
131
+ # latency_ms: 150,
132
+ # status: :success,
133
+ # user: current_user,
134
+ # tags: ["greeting"]
135
+ # )
136
+ #
137
+ # @example Recording a failed request
138
+ # TraceBook.record!(
139
+ # provider: "anthropic",
140
+ # model: "claude-3-5-sonnet",
141
+ # request_payload: request,
142
+ # response_payload: nil,
143
+ # status: :error,
144
+ # error_class: "Faraday::TimeoutError",
145
+ # error_message: "Request timed out after 30s",
146
+ # latency_ms: 30000
147
+ # )
148
+ def record!(**attributes)
149
+ payload = build_normalized_interaction(attributes)
150
+ result = Result.new(idempotency_key: attributes[:idempotency_key])
151
+
152
+ if config.persist_async
153
+ PersistInteractionJob.perform_later(payload.to_h)
154
+ result
155
+ else
156
+ interaction = PersistInteractionJob.perform_now(payload.to_h)
157
+ Result.new(interaction: interaction, idempotency_key: attributes[:idempotency_key])
158
+ end
159
+ rescue StandardError => error
160
+ Result.new(error: error, idempotency_key: attributes[:idempotency_key])
161
+ end
162
+
163
+ private
164
+
165
+ def finalize_configuration!
166
+ config.finalize!
167
+ @configuration_finalized = true
168
+ end
169
+
170
+ def ensure_configurable!
171
+ return unless @configuration_finalized || config.finalized?
172
+
173
+ raise ConfigurationError, "TraceBook configuration is already finalized"
174
+ end
175
+
176
+ def build_normalized_interaction(attributes)
177
+ NormalizedInteraction.new(
178
+ provider: attributes.fetch(:provider),
179
+ model: attributes.fetch(:model),
180
+ project: attributes[:project],
181
+ request_payload: attributes[:request],
182
+ response_payload: attributes[:response],
183
+ request_text: attributes[:request_text],
184
+ response_text: attributes[:response_text],
185
+ input_tokens: attributes[:input_tokens],
186
+ output_tokens: attributes[:output_tokens],
187
+ latency_ms: attributes[:latency_ms],
188
+ status: attributes.fetch(:status, "success"),
189
+ error_class: attributes[:error_class],
190
+ error_message: attributes[:error_message],
191
+ tags: Array(attributes[:tags]).compact,
192
+ metadata: attributes[:metadata] || {},
193
+ user: attributes[:user],
194
+ parent_id: attributes[:parent_id],
195
+ session_id: attributes[:session_id]
196
+ )
197
+ end
198
+ end
199
+ end
200
+
201
+ TraceBook = Tracebook unless defined?(TraceBook)
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tracebook
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - dpaluy
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 8.1.1
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 8.1.1
26
+ - !ruby/object:Gem::Dependency
27
+ name: turbo-rails
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 2.0.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 2.0.0
40
+ - !ruby/object:Gem::Dependency
41
+ name: stimulus-rails
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '1.3'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '1.3'
54
+ - !ruby/object:Gem::Dependency
55
+ name: csv
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.3'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.3'
68
+ description: TraceBook provides a Rails engine for capturing, storing, and reviewing
69
+ LLM API interactions with built-in support for OpenAI, Anthropic, and Ollama. Features
70
+ include PII redaction, cost tracking, review workflows, and export capabilities.
71
+ email:
72
+ - dpaluy@users.noreply.github.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files:
76
+ - MIT-LICENSE
77
+ - README.md
78
+ files:
79
+ - ".yardopts"
80
+ - CHANGELOG.md
81
+ - MIT-LICENSE
82
+ - README.md
83
+ - Rakefile
84
+ - app/assets/images/tracebook/.keep
85
+ - app/assets/javascripts/tracebook/application.js
86
+ - app/assets/stylesheets/tracebook/application.css
87
+ - app/controllers/concerns/.keep
88
+ - app/controllers/tracebook/application_controller.rb
89
+ - app/controllers/tracebook/exports_controller.rb
90
+ - app/controllers/tracebook/interactions_controller.rb
91
+ - app/helpers/tracebook/application_helper.rb
92
+ - app/helpers/tracebook/interactions_helper.rb
93
+ - app/jobs/tracebook/application_job.rb
94
+ - app/jobs/tracebook/daily_rollups_job.rb
95
+ - app/jobs/tracebook/export_job.rb
96
+ - app/jobs/tracebook/persist_interaction_job.rb
97
+ - app/mailers/tracebook/application_mailer.rb
98
+ - app/models/concerns/.keep
99
+ - app/models/tracebook/application_record.rb
100
+ - app/models/tracebook/interaction.rb
101
+ - app/models/tracebook/pricing_rule.rb
102
+ - app/models/tracebook/redaction_rule.rb
103
+ - app/models/tracebook/rollup_daily.rb
104
+ - app/views/layouts/tracebook/application.html.erb
105
+ - app/views/tracebook/interactions/index.html.erb
106
+ - app/views/tracebook/interactions/show.html.erb
107
+ - config/routes.rb
108
+ - db/migrate/20241112000100_create_tracebook_interactions.rb
109
+ - db/migrate/20241112000200_create_tracebook_rollups_dailies.rb
110
+ - db/migrate/20241112000300_create_tracebook_pricing_rules.rb
111
+ - db/migrate/20241112000400_create_tracebook_redaction_rules.rb
112
+ - lib/tasks/tracebook_tasks.rake
113
+ - lib/tasks/yard.rake
114
+ - lib/tracebook.rb
115
+ - lib/tracebook/adapters.rb
116
+ - lib/tracebook/adapters/active_agent.rb
117
+ - lib/tracebook/adapters/ruby_llm.rb
118
+ - lib/tracebook/config.rb
119
+ - lib/tracebook/engine.rb
120
+ - lib/tracebook/errors.rb
121
+ - lib/tracebook/mappers.rb
122
+ - lib/tracebook/mappers/anthropic.rb
123
+ - lib/tracebook/mappers/base.rb
124
+ - lib/tracebook/mappers/ollama.rb
125
+ - lib/tracebook/mappers/openai.rb
126
+ - lib/tracebook/normalized_interaction.rb
127
+ - lib/tracebook/pricing.rb
128
+ - lib/tracebook/pricing/calculator.rb
129
+ - lib/tracebook/redaction_pipeline.rb
130
+ - lib/tracebook/redactors.rb
131
+ - lib/tracebook/redactors/base.rb
132
+ - lib/tracebook/redactors/card_pan.rb
133
+ - lib/tracebook/redactors/email.rb
134
+ - lib/tracebook/redactors/phone.rb
135
+ - lib/tracebook/result.rb
136
+ - lib/tracebook/version.rb
137
+ homepage: https://github.com/dpaluy/tracebook
138
+ licenses:
139
+ - MIT
140
+ metadata:
141
+ rubygems_mfa_required: 'true'
142
+ homepage_uri: https://github.com/dpaluy/tracebook
143
+ documentation_uri: https://rubydoc.info/gems/tracebook
144
+ source_code_uri: https://github.com/dpaluy/tracebook
145
+ changelog_uri: https://github.com/dpaluy/tracebook/blob/main/CHANGELOG.md
146
+ bug_tracker_uri: https://github.com/dpaluy/tracebook/issues
147
+ rdoc_options: []
148
+ require_paths:
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: 3.2.0
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ requirements: []
161
+ rubygems_version: 4.0.0
162
+ specification_version: 4
163
+ summary: Rails engine for LLM interaction telemetry and review.
164
+ test_files: []