bitfab 0.23.6 → 0.23.8

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: fc6488982aa89cb2226e627f80da9bd85a1575bac14728aa81d2d5974c920aae
4
- data.tar.gz: 519b07afa8c9a7220d730b7bc72b5e8a3cddb037b3e2b70bd36b476c4d400d29
3
+ metadata.gz: 342b5de3b40edd60f060465123f21d1c5338063340b34ac7bb7067c7baedfe55
4
+ data.tar.gz: 2f73bec442f3570efe78253d28733942e00b46c03a26ce2731a72e778cd6d79f
5
5
  SHA512:
6
- metadata.gz: 362409a4955d1eec6ac5b4c0309a87d65ce9722f517f4cc364931f89fb66adf1a5fe0b4ac1d5a847a7e9d7fc9559c4d2449de03b2c2d9900ded22e317db28518
7
- data.tar.gz: 735836a1730cf48e2fb5370f755e667bf3912b96a21ca9b97375b7e4e1bfd43e34531e557c5d49f28482dbbe32c091ec5bb08cc86fc9656b4f44a6edd7880b0f
6
+ metadata.gz: c76023257bcdd959cb49d6881faf05e85013b2d30c02826fd57a2a2aa8a72d72d7f8f10716f9f4b2d850a84dfb4a6ef7151077b88f43e2e1e7bd5b4c81ed23ff
7
+ data.tar.gz: 37fb2e1215527c780253dd35e10e4ff01d735336d068d5d8ac16a75d4fcc17e1749f7718cf8ffc550e965a27a9f0ee8fb49b2b847724c10a1ae031f0f1470574
data/lib/bitfab/client.rb CHANGED
@@ -13,6 +13,7 @@ require_relative "warn_once"
13
13
  module Bitfab
14
14
  class Client
15
15
  SPAN_TYPES = %w[llm agent function guardrail handoff custom].freeze
16
+ UUID_PATTERN = /\A[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i
16
17
 
17
18
  # Sentinel returned by check_mock_replay when this span should run real
18
19
  # code (no mock active, wrong strategy, or no matching historical entry).
@@ -115,6 +116,26 @@ module Bitfab
115
116
  BitfabFunction.new(self, trace_function_key)
116
117
  end
117
118
 
119
+ # Fetch one persisted span without loading the full trace.
120
+ # Exactly one of id or name is required. Name lookups return the last
121
+ # matching span by default; occurrence also accepts "first" or a zero-based
122
+ # Integer index.
123
+ def get_trace_span(trace_id, id: nil, name: nil, occurrence: "last")
124
+ validate_trace_id(trace_id)
125
+ raise ArgumentError, "Provide exactly one of id or name" if id.nil? == name.nil?
126
+ validate_span_id(id) unless id.nil?
127
+ if !name.nil? && (!name.is_a?(String) || name.empty?)
128
+ raise ArgumentError, "name must be a non-empty string"
129
+ end
130
+
131
+ valid_occurrence = %w[first last].include?(occurrence) || (occurrence.is_a?(Integer) && occurrence >= 0)
132
+ unless valid_occurrence
133
+ raise ArgumentError, 'occurrence must be "first", "last", or a non-negative integer'
134
+ end
135
+
136
+ @http_client.get_trace_span(trace_id, id:, name:, occurrence:)
137
+ end
138
+
118
139
  # Execute a block inside a span context, sending trace data on completion.
119
140
  # Called by Traceable, not intended for direct use.
120
141
  def execute_span(trace_function_key:, span_name:, span_type:, function_name:, args:, kwargs:,
@@ -343,6 +364,14 @@ module Bitfab
343
364
 
344
365
  private
345
366
 
367
+ def validate_trace_id(trace_id)
368
+ raise ArgumentError, "trace_id must be a valid Bitfab trace ID" unless trace_id.is_a?(String) && UUID_PATTERN.match?(trace_id)
369
+ end
370
+
371
+ def validate_span_id(id)
372
+ raise ArgumentError, "id must be a valid Bitfab span ID" unless id.is_a?(String) && UUID_PATTERN.match?(id)
373
+ end
374
+
346
375
  # Resolve the API key lazily, the first time a span actually needs it.
347
376
  #
348
377
  # The key is intentionally NOT read at construction: a client built when a
@@ -462,6 +491,7 @@ module Bitfab
462
491
  end
463
492
 
464
493
  payload = {
494
+ "id" => trace_id,
465
495
  "type" => "sdk-function",
466
496
  "source" => "ruby-sdk-function",
467
497
  "traceFunctionKey" => trace_function_key,
@@ -492,7 +522,7 @@ module Bitfab
492
522
 
493
523
  def send_span(trace_function_key:, trace_id:, span_id:, parent_span_id:,
494
524
  span_name:, span_type:, function_name:, contexts:, prompt:, args:, kwargs:, result:, error:,
495
- started_at:, ended_at:, test_run_id: nil, input_source_span_id: nil)
525
+ started_at:, ended_at:, test_run_id: nil, input_source_span_id: nil, mocked: false)
496
526
  # If drop() was called on this trace, suppress the span PAYLOAD upload for
497
527
  # every span that completes after the flag was set. The trace completion
498
528
  # still rides out with dropped: true, so the server scrubs any sibling
@@ -540,6 +570,8 @@ module Bitfab
540
570
  raw_span["input_source_span_id"] = input_source_span_id if input_source_span_id
541
571
 
542
572
  payload = {
573
+ "id" => span_id,
574
+ "traceId" => trace_id,
543
575
  "type" => "sdk-function",
544
576
  "source" => "ruby-sdk-function",
545
577
  "sourceTraceId" => trace_id,
@@ -547,6 +579,10 @@ module Bitfab
547
579
  "rawSpan" => raw_span
548
580
  }
549
581
  payload["testRunId"] = test_run_id if test_run_id
582
+ # Flag spans served from the original trace's recorded output during a
583
+ # replay so the trace view can mark them (matches the Python and
584
+ # TypeScript SDKs).
585
+ payload["mocked"] = true if mocked
550
586
 
551
587
  # A value that could only be captured as a placeholder makes the span
552
588
  # non-replayable; record it on the payload's errors (matching the Python
@@ -644,7 +680,8 @@ module Bitfab
644
680
  started_at:,
645
681
  ended_at:,
646
682
  test_run_id:,
647
- input_source_span_id:
683
+ input_source_span_id:,
684
+ mocked: true
648
685
  )
649
686
  # Mocked spans are always non-root (advance_mock_counter returns nil for
650
687
  # root spans, so check_mock_replay never short-circuits them), so the
@@ -80,7 +80,7 @@ module Bitfab
80
80
  http.open_timeout = request_timeout
81
81
  http.read_timeout = request_timeout
82
82
 
83
- req = Net::HTTP::Get.new(uri.path, headers)
83
+ req = Net::HTTP::Get.new(uri.request_uri, headers)
84
84
  response = http.request(req)
85
85
 
86
86
  unless response.is_a?(Net::HTTPSuccess)
@@ -140,6 +140,19 @@ module Bitfab
140
140
  get("/api/sdk/externalSpans/#{span_id}", timeout: 30)
141
141
  end
142
142
 
143
+ def get_trace_span(trace_id, id: nil, name: nil, occurrence: "last")
144
+ query = if id
145
+ {id:}
146
+ else
147
+ {name:, occurrence: occurrence.to_s}
148
+ end
149
+ encoded_trace_id = URI.encode_www_form_component(trace_id)
150
+ get(
151
+ "/api/sdk/traces/#{encoded_trace_id}/span?#{URI.encode_www_form(query)}",
152
+ timeout: 30
153
+ )["span"]
154
+ end
155
+
143
156
  # Fetch the span tree rooted at an external span. Blocking GET request.
144
157
  # Used by replay when a mock strategy is active so child spans can be
145
158
  # matched against their historical outputs.
@@ -7,6 +7,11 @@ module Bitfab
7
7
  @span_state = span_state
8
8
  end
9
9
 
10
+ # The Bitfab ID for the current span.
11
+ def id
12
+ @span_state[:span_id]
13
+ end
14
+
10
15
  # The trace ID for the current span.
11
16
  def trace_id
12
17
  @span_state[:trace_id]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitfab
4
- VERSION = "0.23.6"
4
+ VERSION = "0.23.8"
5
5
  end
data/lib/bitfab.rb CHANGED
@@ -18,6 +18,10 @@ module Bitfab
18
18
  # No-op span handle returned when outside a span context.
19
19
  # All methods do nothing, preventing crashes when called outside traced code.
20
20
  class NoOpCurrentSpan
21
+ def id
22
+ ""
23
+ end
24
+
21
25
  def trace_id
22
26
  ""
23
27
  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.23.6
4
+ version: 0.23.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harvest Team