bitfab 0.21.1 → 0.21.2

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: 364e0ec3b3aa00cc47a398872911e18b1a3e5253f4448214da67f9c12af51814
4
- data.tar.gz: 482340c71e8d2cb0f8ed92efa7a214278529a670e836e46b6209cc18126f230b
3
+ metadata.gz: 910352d2c5434b3fae7c3c56ccfea74dc0a22ce35d0cb4d2cb9d463e4ba3660b
4
+ data.tar.gz: 2e4e07a7447b75404e753f9a2d45350edfbee088ba100aff06031cf477cbc59c
5
5
  SHA512:
6
- metadata.gz: d9c10548592e02d52a74b96b268bea01d33200c3ff338b61758ec6d54dc2adcf07ca5c442212d464cb53cb86a1aeeccb7466c9ca71ba49810902ff6f5445b0b5
7
- data.tar.gz: 3f12de6d0a9eeb5f7c8218d977c4d344a972f762f6c7380e5123d8976032c2215f63337698e4c7d6f18fd669786e4fded0084e0be2ef8fce1e7a696324c2f9b5
6
+ metadata.gz: 1d197beb80ef3dcd1e57a8aebbe3aafd39ed000cadb0e1098ca71369d69ba53d4e805626f632b3bfdfb5f0cbe36d12e34d589f0b3def09be7e397bb1363e8b5e
7
+ data.tar.gz: 2f35b7eeba42eb86005e8cb9af3f46654313a84bc375e3c0edbd04e716cd8c511d0d302c2626d5f679b663f3482a24d8ec9b91d9b653fb2da6412aea0da2280b
data/lib/bitfab/client.rb CHANGED
@@ -425,9 +425,12 @@ module Bitfab
425
425
  def send_span(trace_function_key:, trace_id:, span_id:, parent_span_id:,
426
426
  span_name:, span_type:, function_name:, contexts:, prompt:, args:, kwargs:, result:, error:,
427
427
  started_at:, ended_at:, test_run_id: nil, input_source_span_id: nil)
428
- # Human-readable JSON (input/output fields)
429
- human_inputs = Serialize.serialize_inputs(args, kwargs)
430
- human_output = Serialize.serialize_value(result)
428
+ # Human-readable JSON (input/output fields). The *_with_report variants
429
+ # also report what could not be faithfully captured so a lossy span is
430
+ # marked non-replayable below instead of shipping as if it round-trips.
431
+ human_inputs, input_dropped = Serialize.serialize_inputs_with_report(args, kwargs)
432
+ human_output, output_dropped = Serialize.serialize_value_with_report(result)
433
+ dropped = input_dropped + output_dropped
431
434
 
432
435
  # Marshal + Base64 for full Ruby-to-Ruby object reconstruction
433
436
  raw_input = (args.length == 1 && kwargs.empty?) ? args[0] : {args:, kwargs:}
@@ -469,6 +472,18 @@ module Bitfab
469
472
  }
470
473
  payload["testRunId"] = test_run_id if test_run_id
471
474
 
475
+ # A value that could only be captured as a placeholder makes the span
476
+ # non-replayable; record it on the payload's errors (matching the Python
477
+ # and TypeScript SDKs) instead of shipping the lossy capture silently.
478
+ unless dropped.empty?
479
+ names = dropped.uniq.sort.join(", ")
480
+ payload["errors"] = [{
481
+ "source" => "sdk",
482
+ "step" => "serialization_degraded",
483
+ "error" => "non-replayable: could not faithfully capture #{names}"
484
+ }]
485
+ end
486
+
472
487
  @http_client.send_external_span(payload) # Returns the background thread
473
488
  end
474
489
 
@@ -29,28 +29,44 @@ module Bitfab
29
29
  # Without this the wire-side JSON.dump in the http client can produce a
30
30
  # request that times out or gets rejected, leaving a trace with zero spans.
31
31
  def serialize_value(value)
32
- result = serialize_value_inner(value, 0)
33
- return result unless oversized?(result)
32
+ serialize_value_with_report(value).first
33
+ end
34
34
 
35
- unserializable_stub(value, "too_large")
35
+ # Like serialize_value, but also reports what could not be faithfully
36
+ # captured. Returns [result, dropped] where dropped lists the class name (or
37
+ # "max_depth"/"too_large") behind every placeholder the walk had to emit. A
38
+ # non-empty dropped means the capture is lossy, so the send boundary marks
39
+ # the span serialization_degraded (non-replayable) instead of shipping it as
40
+ # if it round-trips. Mirrors the Python/TS SDKs' report serializers.
41
+ def serialize_value_with_report(value)
42
+ dropped = []
43
+ result = serialize_value_inner(value, 0, dropped)
44
+ if oversized?(result)
45
+ dropped << "too_large"
46
+ return [unserializable_stub(value, "too_large"), dropped]
47
+ end
48
+ [result, dropped]
36
49
  rescue StandardError, SystemStackError
37
- unserializable_stub(value, "unexpected_error")
50
+ [unserializable_stub(value, "unexpected_error"), [class_name(value)]]
38
51
  end
39
52
 
40
- def serialize_value_inner(value, depth)
41
- return "<unserializable: max_depth>" if depth > MAX_SERIALIZE_DEPTH
53
+ def serialize_value_inner(value, depth, dropped = [])
54
+ if depth > MAX_SERIALIZE_DEPTH
55
+ dropped << "max_depth"
56
+ return "<unserializable: max_depth>"
57
+ end
42
58
 
43
59
  case value
44
60
  when nil, true, false, Integer, Float, String
45
61
  value
46
62
  when Hash
47
63
  value.each_with_object({}) do |(k, v), acc|
48
- acc[safe_to_s(k)] = serialize_value_inner(v, depth + 1)
64
+ acc[safe_to_s(k)] = serialize_value_inner(v, depth + 1, dropped)
49
65
  end
50
66
  when Array
51
- value.map { |v| serialize_value_inner(v, depth + 1) }
67
+ value.map { |v| serialize_value_inner(v, depth + 1, dropped) }
52
68
  when Set
53
- value.map { |v| serialize_value_inner(v, depth + 1) }
69
+ value.map { |v| serialize_value_inner(v, depth + 1, dropped) }
54
70
  when Time, DateTime
55
71
  safely_call(value, "iso8601", 3) { safe_to_s(value) }
56
72
  when Date
@@ -59,16 +75,26 @@ module Bitfab
59
75
  value.to_s
60
76
  else
61
77
  if value.respond_to?(:to_h)
62
- h = safely_call(value, "to_h") { return unserializable_stub(value, "to_h_raised") }
63
- serialize_value_inner(h, depth + 1)
78
+ h = safely_call(value, "to_h") do
79
+ dropped << class_name(value)
80
+ return unserializable_stub(value, "to_h_raised")
81
+ end
82
+ serialize_value_inner(h, depth + 1, dropped)
64
83
  elsif value.respond_to?(:to_a)
65
- a = safely_call(value, "to_a") { return unserializable_stub(value, "to_a_raised") }
66
- serialize_value_inner(a, depth + 1)
84
+ a = safely_call(value, "to_a") do
85
+ dropped << class_name(value)
86
+ return unserializable_stub(value, "to_a_raised")
87
+ end
88
+ serialize_value_inner(a, depth + 1, dropped)
67
89
  else
90
+ # An arbitrary object with no structured conversion: stringifying it is
91
+ # a lossy capture (a repr, not its data), so report it as dropped.
92
+ dropped << class_name(value)
68
93
  safe_to_s(value)
69
94
  end
70
95
  end
71
96
  rescue StandardError, SystemStackError
97
+ dropped << class_name(value)
72
98
  unserializable_stub(value, "inner_error")
73
99
  end
74
100
 
@@ -108,14 +134,27 @@ module Bitfab
108
134
 
109
135
  # Serialize function inputs (args + kwargs) for span data (human-readable).
110
136
  def serialize_inputs(args, kwargs = {})
111
- serialized = args.map { |arg| serialize_value(arg) }
137
+ serialize_inputs_with_report(args, kwargs).first
138
+ end
139
+
140
+ # Like serialize_inputs, but also returns the accumulated dropped list so the
141
+ # send boundary can mark a lossy capture non-replayable.
142
+ def serialize_inputs_with_report(args, kwargs = {})
143
+ dropped = []
144
+ serialized = args.map do |arg|
145
+ result, arg_dropped = serialize_value_with_report(arg)
146
+ dropped.concat(arg_dropped)
147
+ result
148
+ end
112
149
  unless kwargs.empty?
113
150
  kw = kwargs.each_with_object({}) do |(k, v), acc|
114
- acc[safe_to_s(k)] = serialize_value(v)
151
+ result, v_dropped = serialize_value_with_report(v)
152
+ dropped.concat(v_dropped)
153
+ acc[safe_to_s(k)] = result
115
154
  end
116
155
  serialized << kw
117
156
  end
118
- serialized
157
+ [serialized, dropped]
119
158
  end
120
159
 
121
160
  # Marshal a value to a Base64-encoded string for Ruby-to-Ruby reconstruction.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitfab
4
- VERSION = "0.21.1"
4
+ VERSION = "0.21.2"
5
5
  end
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.21.1
4
+ version: 0.21.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harvest Team