bitfab 0.66.0 → 0.67.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 +4 -4
- data/lib/bitfab/jev.rb +149 -0
- data/lib/bitfab/subtree.rb +38 -4
- data/lib/bitfab/traceable.rb +3 -1
- data/lib/bitfab/version.rb +1 -1
- data/lib/bitfab.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 455ac9a2dfa11b39c17550b3f86f6ffa87f3ea0a9684fb032722184a705da7e0
|
|
4
|
+
data.tar.gz: 11c0f2491a21515f3fc75f4f4f8eb98f544b7e6d5d3c9d1c7fc09e0728787b16
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d1f41b10072dd2b9b52f2a91ef5265a91805d2aa8129a05a30a303f6f55831934c8206eca6fd215ac938a526f499e9bc2b38a2e74476c8aa06e6c675e14d9b7f
|
|
7
|
+
data.tar.gz: d1e5d1ac8216e5505b5d5da628d6914349fe7ee6018fa55616b20a5ba3ca66e647484f4db176ee85feac28c476f702e831bfcf9ae3fa5ff5084d9d0570584ade
|
data/lib/bitfab/jev.rb
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bitfab
|
|
4
|
+
module Jev
|
|
5
|
+
PROVIDER = "typesafe"
|
|
6
|
+
SYSTEM_ONE = :system_one
|
|
7
|
+
WRAPPER_SCOPE_KEY = :bitfab_jev_wrapper_scope
|
|
8
|
+
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def jev_shaped?(reply)
|
|
12
|
+
return false if reply.nil? || reply.is_a?(String) || reply.is_a?(Numeric) || reply.is_a?(Symbol)
|
|
13
|
+
|
|
14
|
+
usage = field(reply, :usage)
|
|
15
|
+
field(reply, :model).is_a?(String) && !usage.nil? && !usage.is_a?(String) && !usage.is_a?(Numeric)
|
|
16
|
+
rescue
|
|
17
|
+
false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def auto_context(reply, duration_ms)
|
|
21
|
+
jev_shaped?(reply) ? context(reply, duration_ms) : nil
|
|
22
|
+
rescue
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def mark_recorded(reply)
|
|
27
|
+
Thread.current[WRAPPER_SCOPE_KEY]&.push(reply)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def watch_recorded
|
|
31
|
+
previous = Thread.current[WRAPPER_SCOPE_KEY]
|
|
32
|
+
scope = []
|
|
33
|
+
Thread.current[WRAPPER_SCOPE_KEY] = scope
|
|
34
|
+
reply = yield
|
|
35
|
+
[reply, !scope.empty?]
|
|
36
|
+
ensure
|
|
37
|
+
Thread.current[WRAPPER_SCOPE_KEY] = previous
|
|
38
|
+
previous&.concat(scope) if scope
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def record_node_call(method_name)
|
|
42
|
+
return yield unless method_name.to_sym == SYSTEM_ONE
|
|
43
|
+
|
|
44
|
+
span = Bitfab.current_span
|
|
45
|
+
started_at = monotonic_ms
|
|
46
|
+
reply, recorded_inside = watch_recorded { yield }
|
|
47
|
+
begin
|
|
48
|
+
entry = recorded_inside ? nil : auto_context(reply, monotonic_ms - started_at)
|
|
49
|
+
if entry
|
|
50
|
+
span.add_context(entry)
|
|
51
|
+
mark_recorded(reply)
|
|
52
|
+
Subtree.active_sessions.each(&:jev_recorded_below!)
|
|
53
|
+
end
|
|
54
|
+
rescue
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
reply
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def field(value, name)
|
|
61
|
+
if value.is_a?(Hash)
|
|
62
|
+
value.key?(name) ? value[name] : value[name.to_s]
|
|
63
|
+
elsif value.respond_to?(name)
|
|
64
|
+
value.public_send(name)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def context(reply, duration_ms)
|
|
69
|
+
return nil if reply.nil?
|
|
70
|
+
|
|
71
|
+
usage = field(reply, :usage)
|
|
72
|
+
model = field(reply, :model)
|
|
73
|
+
input_tokens = usage.nil? ? nil : field(usage, :input_tokens)
|
|
74
|
+
output_tokens = usage.nil? ? nil : field(usage, :output_tokens)
|
|
75
|
+
entry = {provider: PROVIDER}
|
|
76
|
+
entry[:model] = model if model.is_a?(String)
|
|
77
|
+
entry[:inputTokens] = input_tokens if input_tokens.is_a?(Numeric)
|
|
78
|
+
entry[:outputTokens] = output_tokens if output_tokens.is_a?(Numeric)
|
|
79
|
+
entry[:durationMs] = duration_ms
|
|
80
|
+
entry
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def record(span, reply, duration_ms)
|
|
84
|
+
entry = context(reply, duration_ms)
|
|
85
|
+
span.add_context(entry) if entry
|
|
86
|
+
rescue
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def monotonic_ms
|
|
91
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class Wrapper
|
|
95
|
+
def initialize(callable, on_reply)
|
|
96
|
+
@callable = callable
|
|
97
|
+
@on_reply = on_reply
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Call the wrapped Jev method with the given arguments and return its
|
|
101
|
+
# reply unchanged. Errors from the call are raised as-is and record nothing.
|
|
102
|
+
def call(*args, **kwargs, &block)
|
|
103
|
+
span = Bitfab.current_span
|
|
104
|
+
started_at = Jev.monotonic_ms
|
|
105
|
+
reply, recorded = Jev.watch_recorded { @callable.call(*args, **kwargs, &block) }
|
|
106
|
+
Jev.record(span, reply, Jev.monotonic_ms - started_at) unless recorded
|
|
107
|
+
begin
|
|
108
|
+
@on_reply&.call(reply)
|
|
109
|
+
rescue
|
|
110
|
+
nil
|
|
111
|
+
end
|
|
112
|
+
reply
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# A Proc that calls this wrapper, so it can be passed with `&`.
|
|
116
|
+
def to_proc
|
|
117
|
+
method(:call).to_proc
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
class Client
|
|
123
|
+
# Record the model, token usage, and duration of a Jev (TypeSafe System One)
|
|
124
|
+
# call on the span that is current when the call starts. Opens no span of
|
|
125
|
+
# its own and never raises from recording.
|
|
126
|
+
#
|
|
127
|
+
# @example
|
|
128
|
+
# ask_jev = Bitfab.client.wrap_jev(jev.method(:system_one))
|
|
129
|
+
# reply = ask_jev.call(state:, questions:)
|
|
130
|
+
#
|
|
131
|
+
# @param callable [#call, nil] calls Jev and returns its reply; pass a block instead to wrap the block
|
|
132
|
+
# @param on_reply [#call, nil] called with each reply after it is recorded; its errors are ignored
|
|
133
|
+
# @return [Bitfab::Jev::Wrapper] responds to +call+ with the same arguments as the wrapped callable
|
|
134
|
+
def wrap_jev(callable = nil, on_reply: nil, &block)
|
|
135
|
+
raise ArgumentError, "wrap_jev takes a callable or a block, not both." if callable && block
|
|
136
|
+
target = callable || block
|
|
137
|
+
raise ArgumentError, "wrap_jev requires a callable that calls Jev (e.g., jev.method(:system_one))." unless target.respond_to?(:call)
|
|
138
|
+
|
|
139
|
+
Jev::Wrapper.new(target, on_reply)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class BitfabFunction
|
|
144
|
+
# Jev wrappers record onto the currently active span under any key.
|
|
145
|
+
def wrap_jev(*args, **kwargs, &block)
|
|
146
|
+
@client.wrap_jev(*args, **kwargs, &block)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
data/lib/bitfab/subtree.rb
CHANGED
|
@@ -37,6 +37,7 @@ module Bitfab
|
|
|
37
37
|
:name,
|
|
38
38
|
:excluded,
|
|
39
39
|
:passthrough,
|
|
40
|
+
:jev,
|
|
40
41
|
keyword_init: true
|
|
41
42
|
)
|
|
42
43
|
|
|
@@ -55,6 +56,8 @@ module Bitfab
|
|
|
55
56
|
:declared_node,
|
|
56
57
|
:contexts,
|
|
57
58
|
:prompt,
|
|
59
|
+
:jev_started_at,
|
|
60
|
+
:jev_recorded_below,
|
|
58
61
|
keyword_init: true
|
|
59
62
|
)
|
|
60
63
|
|
|
@@ -506,7 +509,7 @@ module Bitfab
|
|
|
506
509
|
|
|
507
510
|
return if event.event == :call && skip_boundary_call?(event)
|
|
508
511
|
|
|
509
|
-
resolved_path =
|
|
512
|
+
resolved_path = traced_path(event)
|
|
510
513
|
return unless resolved_path
|
|
511
514
|
|
|
512
515
|
case event.event
|
|
@@ -523,6 +526,12 @@ module Bitfab
|
|
|
523
526
|
@exclude[method_name.to_s] == true
|
|
524
527
|
end
|
|
525
528
|
|
|
529
|
+
def jev_recorded_below!
|
|
530
|
+
@stacks[Fiber.current.object_id]&.each do |entry|
|
|
531
|
+
entry.jev_recorded_below = true if entry.is_a?(OpenSpan)
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
|
|
526
535
|
def reserve_node
|
|
527
536
|
stack = current_stack(Fiber.current.object_id)
|
|
528
537
|
return true if captured_depth(stack) < @max_depth
|
|
@@ -684,7 +693,9 @@ module Bitfab
|
|
|
684
693
|
args:,
|
|
685
694
|
kwargs:,
|
|
686
695
|
started_at: now,
|
|
687
|
-
span_data: without_content ? {"content_off_by_simulation_plan" => true} : nil
|
|
696
|
+
span_data: without_content ? {"content_off_by_simulation_plan" => true} : nil,
|
|
697
|
+
span_type: metadata.jev ? "llm" : nil,
|
|
698
|
+
jev_started_at: metadata.jev ? Jev.monotonic_ms : nil
|
|
688
699
|
)
|
|
689
700
|
end
|
|
690
701
|
|
|
@@ -708,6 +719,7 @@ module Bitfab
|
|
|
708
719
|
return unless entry.is_a?(OpenSpan) && matching_identity?(entry.identity, event, resolved_path)
|
|
709
720
|
|
|
710
721
|
stack.pop
|
|
722
|
+
record_jev_reply(entry, event.return_value) if entry.jev_started_at
|
|
711
723
|
emit(entry, result: event.return_value)
|
|
712
724
|
parent = stack.last
|
|
713
725
|
parent.error ||= entry.error if entry.error && parent.is_a?(OpenSpan)
|
|
@@ -715,6 +727,17 @@ module Bitfab
|
|
|
715
727
|
@drained = true if @exhausted && captured_calls_drained?
|
|
716
728
|
end
|
|
717
729
|
|
|
730
|
+
def record_jev_reply(entry, reply)
|
|
731
|
+
return if entry.error || entry.jev_recorded_below
|
|
732
|
+
|
|
733
|
+
context = Jev.auto_context(reply, Jev.monotonic_ms - entry.jev_started_at)
|
|
734
|
+
return unless context
|
|
735
|
+
|
|
736
|
+
entry.contexts = [context]
|
|
737
|
+
Jev.mark_recorded(reply)
|
|
738
|
+
jev_recorded_below!
|
|
739
|
+
end
|
|
740
|
+
|
|
718
741
|
def record_error(event)
|
|
719
742
|
entry = @stacks[Fiber.current.object_id]&.last
|
|
720
743
|
return unless entry.is_a?(OpenSpan)
|
|
@@ -849,7 +872,8 @@ module Bitfab
|
|
|
849
872
|
lineno: event.lineno,
|
|
850
873
|
name: span_name(event, method_id),
|
|
851
874
|
excluded: @exclude[method_id.to_s],
|
|
852
|
-
passthrough: passthrough_wrapper?(event.parameters)
|
|
875
|
+
passthrough: passthrough_wrapper?(event.parameters),
|
|
876
|
+
jev: method_id == Jev::SYSTEM_ONE
|
|
853
877
|
).freeze
|
|
854
878
|
end
|
|
855
879
|
|
|
@@ -862,6 +886,16 @@ module Bitfab
|
|
|
862
886
|
event.callee_id || event.method_id
|
|
863
887
|
end
|
|
864
888
|
|
|
889
|
+
def traced_path(event)
|
|
890
|
+
owned_path(event.path) || jev_path(event)
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
def jev_path(event)
|
|
894
|
+
return nil unless called_method_id(event) == Jev::SYSTEM_ONE && !sdk_frame?(event.path)
|
|
895
|
+
|
|
896
|
+
Subtree.realpath(event.path)
|
|
897
|
+
end
|
|
898
|
+
|
|
865
899
|
def owned_path(path)
|
|
866
900
|
@owned_paths.fetch(path) do
|
|
867
901
|
resolved = Subtree.realpath(path)
|
|
@@ -945,7 +979,7 @@ module Bitfab
|
|
|
945
979
|
return unless event.event == :call
|
|
946
980
|
return if skip_boundary_call?(event)
|
|
947
981
|
|
|
948
|
-
resolved_path =
|
|
982
|
+
resolved_path = traced_path(event)
|
|
949
983
|
count_dropped_call(event, resolved_path) if resolved_path
|
|
950
984
|
rescue Exception # rubocop:disable Lint/RescueException
|
|
951
985
|
nil
|
data/lib/bitfab/traceable.rb
CHANGED
|
@@ -173,7 +173,9 @@ module Bitfab
|
|
|
173
173
|
explicit_span_receiver: self, explicit_span_method_name: method_name,
|
|
174
174
|
experiment_id:, finalize:, surface: :trace, result_recorded_elsewhere: call.recorded_elsewhere?, declared_node: true,
|
|
175
175
|
on_start: call.method(:start), on_complete: call.method(:complete),
|
|
176
|
-
around_body: call.method(:run))
|
|
176
|
+
around_body: call.method(:run)) do
|
|
177
|
+
Jev.record_node_call(method_name) { super(*args, **kwargs, &block) }
|
|
178
|
+
end
|
|
177
179
|
end
|
|
178
180
|
end
|
|
179
181
|
end
|
data/lib/bitfab/version.rb
CHANGED
data/lib/bitfab.rb
CHANGED
|
@@ -30,6 +30,7 @@ require_relative "bitfab/replay_branch"
|
|
|
30
30
|
require_relative "bitfab/client"
|
|
31
31
|
require_relative "bitfab/seed"
|
|
32
32
|
require_relative "bitfab/baml"
|
|
33
|
+
require_relative "bitfab/jev"
|
|
33
34
|
require_relative "bitfab/traceable"
|
|
34
35
|
require_relative "bitfab/replay_registry"
|
|
35
36
|
require_relative "bitfab/seed_cli"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bitfab
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.67.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Harvest Team
|
|
@@ -175,6 +175,7 @@ files:
|
|
|
175
175
|
- lib/bitfab/git_state.rb
|
|
176
176
|
- lib/bitfab/graders.rb
|
|
177
177
|
- lib/bitfab/http_client.rb
|
|
178
|
+
- lib/bitfab/jev.rb
|
|
178
179
|
- lib/bitfab/labels.rb
|
|
179
180
|
- lib/bitfab/mock_override.rb
|
|
180
181
|
- lib/bitfab/organization_members.rb
|