bitfab 0.9.0 → 0.10.4
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/client.rb +85 -20
- data/lib/bitfab/replay.rb +28 -3
- data/lib/bitfab/span_context.rb +4 -3
- data/lib/bitfab/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e5d6df92ce06e91ff963c8dc0e78af3a0f591c51b53330c1c8630bb5d66e7663
|
|
4
|
+
data.tar.gz: 199d4883cfad921563b3b5786d2db5b017d4b618e000c7d259ed9f69be3eb7ba
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 02c87809df7f4039dd608d3e60ddc0ff85b791d834ea7dc5557a368987d5dba1a174705074a26a9df80d4a8bb7c4ff7493f4c8ac03beb457629d896768a70567
|
|
7
|
+
data.tar.gz: 579f94120498658ceeb5129b3a21a9d1477d816be9216963c168e0c8747aef0687298f632539f33ace2960db53a19a9ab7847f776d27d971e1b006ad1ebbc909
|
data/lib/bitfab/client.rb
CHANGED
|
@@ -53,9 +53,13 @@ module Bitfab
|
|
|
53
53
|
is_root_span = parent_span_id.nil?
|
|
54
54
|
started_at = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ")
|
|
55
55
|
|
|
56
|
+
replay_ctx = ReplayContext.current
|
|
57
|
+
resolved_test_run_id = replay_ctx&.dig(:test_run_id)
|
|
58
|
+
resolved_input_source_span_id = replay_ctx&.dig(:input_source_span_id)
|
|
59
|
+
|
|
56
60
|
# Register trace state for root spans
|
|
57
61
|
if is_root_span && !TraceState.get(trace_id)
|
|
58
|
-
TraceState.create(trace_id)
|
|
62
|
+
TraceState.create(trace_id, test_run_id: resolved_test_run_id)
|
|
59
63
|
end
|
|
60
64
|
|
|
61
65
|
if is_root_span
|
|
@@ -66,27 +70,18 @@ module Bitfab
|
|
|
66
70
|
error = nil
|
|
67
71
|
span_contexts = nil
|
|
68
72
|
span_prompt = nil
|
|
73
|
+
finalized = false
|
|
74
|
+
|
|
75
|
+
finalize = lambda do |final_result, final_error|
|
|
76
|
+
# Never crash the host app due to span building/sending. Idempotent —
|
|
77
|
+
# only the first call sends the span. Subsequent calls (e.g. from the
|
|
78
|
+
# enumerator wrapper after iteration completes) are no-ops.
|
|
79
|
+
next if finalized
|
|
80
|
+
finalized = true
|
|
69
81
|
|
|
70
|
-
begin
|
|
71
|
-
SpanContext.with_span(trace_id:, span_id:) do
|
|
72
|
-
result = yield
|
|
73
|
-
ensure
|
|
74
|
-
# Capture contexts before the span context is popped
|
|
75
|
-
span_contexts = SpanContext.current&.dig(:contexts)
|
|
76
|
-
span_prompt = SpanContext.current&.dig(:prompt)
|
|
77
|
-
end
|
|
78
|
-
rescue => e
|
|
79
|
-
error = e.message
|
|
80
|
-
raise
|
|
81
|
-
ensure
|
|
82
|
-
# Never crash the host app due to span building/sending
|
|
83
82
|
begin
|
|
84
83
|
ended_at = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ")
|
|
85
84
|
|
|
86
|
-
replay_ctx = ReplayContext.current
|
|
87
|
-
resolved_test_run_id = replay_ctx&.dig(:test_run_id)
|
|
88
|
-
resolved_input_source_span_id = replay_ctx&.dig(:input_source_span_id)
|
|
89
|
-
|
|
90
85
|
span_thread = send_span(
|
|
91
86
|
trace_function_key:,
|
|
92
87
|
trace_id:,
|
|
@@ -99,8 +94,8 @@ module Bitfab
|
|
|
99
94
|
prompt: span_prompt,
|
|
100
95
|
args:,
|
|
101
96
|
kwargs:,
|
|
102
|
-
result
|
|
103
|
-
error
|
|
97
|
+
result: final_result,
|
|
98
|
+
error: final_error,
|
|
104
99
|
started_at:,
|
|
105
100
|
ended_at:,
|
|
106
101
|
test_run_id: resolved_test_run_id,
|
|
@@ -130,11 +125,78 @@ module Bitfab
|
|
|
130
125
|
end
|
|
131
126
|
end
|
|
132
127
|
|
|
128
|
+
begin
|
|
129
|
+
SpanContext.with_span(trace_id:, span_id:) do
|
|
130
|
+
result = yield
|
|
131
|
+
ensure
|
|
132
|
+
# Capture contexts before the span context is popped
|
|
133
|
+
span_contexts = SpanContext.current&.dig(:contexts)
|
|
134
|
+
span_prompt = SpanContext.current&.dig(:prompt)
|
|
135
|
+
end
|
|
136
|
+
rescue => e
|
|
137
|
+
error = e.message
|
|
138
|
+
finalize.call(result, error)
|
|
139
|
+
raise
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# If the wrapped block returned an Enumerator (lazy iteration via
|
|
143
|
+
# `enum_for`, `to_enum`, `Enumerator.new`, `[...].lazy.map(...)`, etc.),
|
|
144
|
+
# the work hasn't actually run yet — the values are produced as the
|
|
145
|
+
# caller iterates. Without special handling we'd close the span here
|
|
146
|
+
# with `result == <the Enumerator object>`, and any nested `bitfab_span`
|
|
147
|
+
# calls inside the enumerator body would see an empty span stack and
|
|
148
|
+
# post their own root traces, fragmenting one logical workflow.
|
|
149
|
+
#
|
|
150
|
+
# Instead, hand the caller a wrapping Enumerator whose body restores
|
|
151
|
+
# the parent span stack on the iterating fiber, drives the source,
|
|
152
|
+
# collects yielded values as the span output, and finalizes the span
|
|
153
|
+
# once iteration completes (or errors).
|
|
154
|
+
#
|
|
155
|
+
# Limitation: when the source enumerator itself runs its body in a
|
|
156
|
+
# separate fiber (e.g. `Enumerator.new { |y| ... }` or `enum_for(...)`
|
|
157
|
+
# without a block), nested `bitfab_span` calls inside that body fiber
|
|
158
|
+
# still see an empty stack because `Thread.current[STACK_KEY]` is
|
|
159
|
+
# fiber-local. Lazy chains over collections (`.lazy.map`) and ordinary
|
|
160
|
+
# `each` callbacks DO run in the iterating fiber and nest correctly.
|
|
161
|
+
if result.is_a?(Enumerator)
|
|
162
|
+
return wrap_enumerator(result, trace_id:, span_id:, finalize:)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
finalize.call(result, error)
|
|
133
166
|
result
|
|
134
167
|
end
|
|
135
168
|
|
|
136
169
|
private
|
|
137
170
|
|
|
171
|
+
# Build an Enumerator that drives `source`, restoring `[trace_id, span_id]`
|
|
172
|
+
# on the iterating fiber so nested `bitfab_span` calls inside lazy / `each`
|
|
173
|
+
# callbacks nest under the parent span. Yielded values are collected as the
|
|
174
|
+
# span output. The span is sent exactly once — when iteration finishes,
|
|
175
|
+
# raises, or the wrapper is `.close`d.
|
|
176
|
+
def wrap_enumerator(source, trace_id:, span_id:, finalize:)
|
|
177
|
+
span_entry = {trace_id:, span_id:}
|
|
178
|
+
yielded = []
|
|
179
|
+
|
|
180
|
+
Enumerator.new do |yielder|
|
|
181
|
+
# Push our span onto this fiber's stack so anything that runs in this
|
|
182
|
+
# fiber (including the user's lazy block and `each` callbacks) sees
|
|
183
|
+
# the right parent.
|
|
184
|
+
SpanContext.stack.push(span_entry)
|
|
185
|
+
begin
|
|
186
|
+
source.each do |value|
|
|
187
|
+
yielded << value
|
|
188
|
+
yielder << value
|
|
189
|
+
end
|
|
190
|
+
finalize.call(yielded, nil)
|
|
191
|
+
rescue => e
|
|
192
|
+
finalize.call(yielded, e.message)
|
|
193
|
+
raise
|
|
194
|
+
ensure
|
|
195
|
+
SpanContext.stack.pop
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
138
200
|
def validate_span_type!(type)
|
|
139
201
|
return if SPAN_TYPES.include?(type.to_s)
|
|
140
202
|
|
|
@@ -169,6 +231,9 @@ module Bitfab
|
|
|
169
231
|
if trace_state&.dig(:session_id)
|
|
170
232
|
payload["sessionId"] = trace_state[:session_id]
|
|
171
233
|
end
|
|
234
|
+
if trace_state&.dig(:test_run_id)
|
|
235
|
+
payload["testRunId"] = trace_state[:test_run_id]
|
|
236
|
+
end
|
|
172
237
|
|
|
173
238
|
@http_client.send_external_trace(payload)
|
|
174
239
|
|
data/lib/bitfab/replay.rb
CHANGED
|
@@ -105,7 +105,8 @@ module Bitfab
|
|
|
105
105
|
def process_single_item(http_client, server_item, receiver, method_name, test_run_id)
|
|
106
106
|
span = http_client.get_external_span(server_item["externalSpanId"])
|
|
107
107
|
item_data = extract_span_data(span)
|
|
108
|
-
|
|
108
|
+
metrics = extract_server_item_metrics(server_item)
|
|
109
|
+
execute_item(item_data, receiver, method_name, test_run_id, span["id"], metrics)
|
|
109
110
|
end
|
|
110
111
|
|
|
111
112
|
# Extract input/output data from an external span's rawData.
|
|
@@ -121,8 +122,29 @@ module Bitfab
|
|
|
121
122
|
}
|
|
122
123
|
end
|
|
123
124
|
|
|
125
|
+
# Pull durationMs / tokens / model from the start-replay server item.
|
|
126
|
+
# Normalizes to symbol-keyed tokens hash and nil-safe defaults so older
|
|
127
|
+
# servers without these fields still produce a consistent shape.
|
|
128
|
+
def extract_server_item_metrics(server_item)
|
|
129
|
+
raw_tokens = server_item["tokens"]
|
|
130
|
+
tokens = if raw_tokens.is_a?(Hash)
|
|
131
|
+
{
|
|
132
|
+
input: raw_tokens["input"],
|
|
133
|
+
output: raw_tokens["output"],
|
|
134
|
+
cached: raw_tokens["cached"],
|
|
135
|
+
total: raw_tokens["total"]
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
{
|
|
140
|
+
duration_ms: server_item["durationMs"],
|
|
141
|
+
tokens:,
|
|
142
|
+
model: server_item["model"]
|
|
143
|
+
}
|
|
144
|
+
end
|
|
145
|
+
|
|
124
146
|
# Execute a single replay item: deserialize inputs, call method with replay context.
|
|
125
|
-
def execute_item(item, receiver, method_name, test_run_id, input_source_span_id = nil)
|
|
147
|
+
def execute_item(item, receiver, method_name, test_run_id, input_source_span_id = nil, metrics = {})
|
|
126
148
|
args, kwargs = Serialize.deserialize_inputs(item)
|
|
127
149
|
|
|
128
150
|
fn_result = nil
|
|
@@ -142,7 +164,10 @@ module Bitfab
|
|
|
142
164
|
input: args,
|
|
143
165
|
result: fn_result,
|
|
144
166
|
original_output: item["output"],
|
|
145
|
-
error: fn_error
|
|
167
|
+
error: fn_error,
|
|
168
|
+
duration_ms: metrics[:duration_ms],
|
|
169
|
+
tokens: metrics[:tokens],
|
|
170
|
+
model: metrics[:model]
|
|
146
171
|
}
|
|
147
172
|
end
|
|
148
173
|
end
|
data/lib/bitfab/span_context.rb
CHANGED
|
@@ -131,12 +131,13 @@ module Bitfab
|
|
|
131
131
|
@states_mutex.synchronize { @states[trace_id] }
|
|
132
132
|
end
|
|
133
133
|
|
|
134
|
-
def create(trace_id)
|
|
134
|
+
def create(trace_id, test_run_id: nil)
|
|
135
135
|
@states_mutex.synchronize do
|
|
136
136
|
@states[trace_id] ||= {
|
|
137
137
|
trace_id:,
|
|
138
|
-
started_at: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ")
|
|
139
|
-
|
|
138
|
+
started_at: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ"),
|
|
139
|
+
test_run_id:
|
|
140
|
+
}.compact
|
|
140
141
|
end
|
|
141
142
|
end
|
|
142
143
|
|
data/lib/bitfab/version.rb
CHANGED