bitfab 0.9.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.
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bitfab
4
+ # Mixin for declarative span tracing on instance methods.
5
+ #
6
+ # @example
7
+ # Bitfab.configure(api_key: "...")
8
+ #
9
+ # class OrderService
10
+ # include Bitfab::Traceable
11
+ # bitfab_function "order-processing"
12
+ #
13
+ # bitfab_span :process_order, type: "function"
14
+ # def process_order(order_id)
15
+ # { total: 100 }
16
+ # end
17
+ #
18
+ # bitfab_span :validate_order, name: "Validate", type: "guardrail"
19
+ # def validate_order(order_id)
20
+ # { valid: true }
21
+ # end
22
+ # end
23
+ #
24
+ module Traceable
25
+ def self.included(base)
26
+ base.extend(ClassMethods)
27
+ end
28
+
29
+ # Wrap an existing method on an external class with span tracing.
30
+ # Use this to trace third-party library calls without modifying their source.
31
+ #
32
+ # @example
33
+ # Bitfab::Traceable.wrap(OpenAI::Client, :chat,
34
+ # trace_function_key: "openai", name: "Chat", type: "llm")
35
+ #
36
+ # @param klass [Class, Module] the class to wrap
37
+ # @param method_name [Symbol] the method to wrap
38
+ # @param trace_function_key [String] the trace function key
39
+ # @param name [String, nil] explicit span name (defaults to method name)
40
+ # @param type [String] span type: llm, agent, function, guardrail, handoff, custom
41
+ def self.wrap(klass, method_name, trace_function_key:, name: nil, type: "custom")
42
+ span_name = name || method_name.to_s
43
+ method_name_str = method_name.to_s
44
+
45
+ wrapper = Module.new do
46
+ define_method(method_name) do |*args, **kwargs, &block|
47
+ Bitfab.client.send(:execute_span,
48
+ trace_function_key:,
49
+ span_name:,
50
+ span_type: type,
51
+ function_name: method_name_str,
52
+ args:,
53
+ kwargs:) do
54
+ super(*args, **kwargs, &block)
55
+ end
56
+ end
57
+ end
58
+
59
+ klass.prepend(wrapper)
60
+ end
61
+
62
+ module ClassMethods
63
+ # Set the trace function key for this class.
64
+ # All spans declared in this class will be grouped under this key.
65
+ #
66
+ # @param key [String] the trace function key
67
+ def bitfab_function(key)
68
+ @bitfab_function_key = key
69
+ end
70
+
71
+ # Declare that a method should be wrapped with span tracing.
72
+ #
73
+ # Supports three styles:
74
+ # bitfab_span :foo, type: "function" # before def foo (uses method_added hook)
75
+ # def foo; end
76
+ #
77
+ # bitfab_span def foo # inline (def returns :foo, method already exists)
78
+ # ...
79
+ # end, type: "function"
80
+ #
81
+ # def foo; end # after def foo (method already exists)
82
+ # bitfab_span :foo, type: "function"
83
+ #
84
+ # @param method_name [Symbol] the method to wrap
85
+ # @param trace_function_key [String, nil] trace function key (overrides class-level bitfab_function)
86
+ # @param name [String, nil] explicit span name (defaults to method name)
87
+ # @param type [String] span type: llm, agent, function, guardrail, handoff, custom
88
+ def bitfab_span(method_name, trace_function_key: nil, name: nil, type: "custom")
89
+ trace_function_key ||= @bitfab_function_key
90
+ unless trace_function_key
91
+ raise "No trace function key provided. Pass `trace_function_key:` to `bitfab_span` " \
92
+ "or call `bitfab_function 'my-key'` before using `bitfab_span`."
93
+ end
94
+
95
+ # If the method already exists (inline or after-method style), wrap it immediately
96
+ if method_defined?(method_name) || private_method_defined?(method_name)
97
+ _bitfab_wrap_method(method_name, trace_function_key:, name:, type:)
98
+ else
99
+ # Method doesn't exist yet (before-method style) — register for method_added hook
100
+ @_bitfab_pending_spans ||= {}
101
+ @_bitfab_pending_spans[method_name] = {
102
+ trace_function_key:,
103
+ name:,
104
+ type:
105
+ }
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ def method_added(method_name)
112
+ super
113
+ return unless defined?(@_bitfab_pending_spans) && @_bitfab_pending_spans&.key?(method_name)
114
+
115
+ config = @_bitfab_pending_spans.delete(method_name)
116
+ _bitfab_wrap_method(method_name, **config)
117
+ end
118
+
119
+ def _bitfab_wrap_method(method_name, trace_function_key:, name: nil, type: "custom")
120
+ span_name = name || method_name.to_s
121
+ method_name_str = method_name.to_s
122
+
123
+ wrapper = Module.new do
124
+ define_method(method_name) do |*args, **kwargs, &block|
125
+ Bitfab.client.send(:execute_span,
126
+ trace_function_key:,
127
+ span_name:,
128
+ span_type: type,
129
+ function_name: method_name_str,
130
+ args:,
131
+ kwargs:) do
132
+ super(*args, **kwargs, &block)
133
+ end
134
+ end
135
+ end
136
+
137
+ prepend(wrapper)
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bitfab
4
+ VERSION = "0.9.0"
5
+ end
data/lib/bitfab.rb ADDED
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "bitfab/version"
4
+ require_relative "bitfab/constants"
5
+ require_relative "bitfab/serialize"
6
+ require_relative "bitfab/span_context"
7
+ require_relative "bitfab/http_client"
8
+ require_relative "bitfab/replay"
9
+ require_relative "bitfab/client"
10
+ require_relative "bitfab/traceable"
11
+
12
+ module Bitfab
13
+ # No-op span handle returned when outside a span context.
14
+ # All methods do nothing, preventing crashes when called outside traced code.
15
+ class NoOpCurrentSpan
16
+ def trace_id
17
+ ""
18
+ end
19
+
20
+ def add_context(_context)
21
+ # No-op
22
+ end
23
+
24
+ def set_prompt(_prompt)
25
+ # No-op
26
+ end
27
+ end
28
+
29
+ # No-op trace handle returned when outside a span context.
30
+ # All methods do nothing, preventing crashes when called outside traced code.
31
+ class NoOpCurrentTrace
32
+ def set_session_id(_session_id)
33
+ # No-op
34
+ end
35
+
36
+ def set_metadata(_metadata)
37
+ # No-op
38
+ end
39
+
40
+ def add_context(_context)
41
+ # No-op
42
+ end
43
+ end
44
+
45
+ NO_OP_SPAN = NoOpCurrentSpan.new.freeze
46
+ NO_OP_TRACE = NoOpCurrentTrace.new.freeze
47
+
48
+ class << self
49
+ # Configure the global Bitfab client.
50
+ #
51
+ # @param api_key [String] API key for authentication
52
+ # @param service_url [String, nil] base URL (default: https://bitfab.ai)
53
+ #
54
+ # @example
55
+ # Bitfab.configure(api_key: ENV["BITFAB_API_KEY"])
56
+ #
57
+ def configure(api_key:, service_url: nil, enabled: true)
58
+ @client = Client.new(api_key:, service_url:, enabled:)
59
+ end
60
+
61
+ # Returns the global client, raising if not configured.
62
+ def client
63
+ @client or raise "Bitfab not configured. Call Bitfab.configure(api_key: '...') first."
64
+ end
65
+
66
+ # Reset the global client (primarily for testing).
67
+ def reset!
68
+ @client = nil
69
+ end
70
+
71
+ # Get a handle to the current active span.
72
+ #
73
+ # Call this from inside a traced method to get a span handle that allows
74
+ # setting metadata at runtime.
75
+ #
76
+ # @return [CurrentSpan, NoOpCurrentSpan] the current span, or a no-op if outside a span context
77
+ def current_span
78
+ entry = SpanContext.current
79
+ return NO_OP_SPAN unless entry
80
+
81
+ CurrentSpan.new(entry)
82
+ end
83
+
84
+ # Get a handle to the current active trace.
85
+ #
86
+ # Call this from inside a traced method to get a trace handle that allows
87
+ # setting trace-level context at runtime.
88
+ #
89
+ # @return [CurrentTrace, NoOpCurrentTrace] the current trace, or a no-op if outside a span context
90
+ def current_trace
91
+ entry = SpanContext.current
92
+ return NO_OP_TRACE unless entry
93
+
94
+ CurrentTrace.new(entry[:trace_id])
95
+ end
96
+ end
97
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bitfab
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Harvest Team
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: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rake
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '13.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '13.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rspec
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.13'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.13'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rubocop
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '1.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '1.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rubocop-standard
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '3.0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '3.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: standard
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '1.0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '1.0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: webmock
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '3.0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '3.0'
110
+ description: Client library for sending function execution spans to the Bitfab API.
111
+ Provides automatic tracing with nested span support.
112
+ email:
113
+ - team@goharvest.ai
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - README.md
119
+ - lib/bitfab.rb
120
+ - lib/bitfab/client.rb
121
+ - lib/bitfab/constants.rb
122
+ - lib/bitfab/http_client.rb
123
+ - lib/bitfab/replay.rb
124
+ - lib/bitfab/serialize.rb
125
+ - lib/bitfab/span_context.rb
126
+ - lib/bitfab/traceable.rb
127
+ - lib/bitfab/version.rb
128
+ homepage: https://bitfab.ai
129
+ licenses:
130
+ - MIT
131
+ metadata:
132
+ homepage_uri: https://bitfab.ai
133
+ source_code_uri: https://bitfab.ai
134
+ rubygems_mfa_required: 'true'
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '3.4'
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubygems_version: 3.6.9
150
+ specification_version: 4
151
+ summary: Bitfab Ruby SDK for function tracing and span management
152
+ test_files: []