simforge 0.6.0 → 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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/simforge/client.rb +48 -7
- data/lib/simforge/constants.rb +1 -0
- data/lib/simforge/http_client.rb +61 -3
- data/lib/simforge/replay.rb +149 -0
- data/lib/simforge/serialize.rb +34 -0
- data/lib/simforge/span_context.rb +14 -1
- data/lib/simforge/version.rb +1 -1
- data/lib/simforge.rb +5 -0
- metadata +19 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2a76d9b40226f43c8a4109f0169d6d8c13858fbbe3a3c6856ff218fed8295d1a
|
|
4
|
+
data.tar.gz: 6ac64f460143f24a0e6eed048396ffd509a61106e741bdc821bc187982786ac1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d466e47c84372946639d021ebdf20ffca1e330f7625f2d2d90e004a2d1c0d4bc4622cccc3e846d1b24918c28062de32e6a60107f2a0fe67bd085649d9e190027
|
|
7
|
+
data.tar.gz: cc2d549e359de49705dc92c0c33be38167041663385d12b4a75d12db02679d3d2271d940c6c0631770cea0da9070d656ab60f49e18f90b48cbcc311e9dc9e270
|
data/README.md
CHANGED
data/lib/simforge/client.rb
CHANGED
|
@@ -5,6 +5,7 @@ require "time"
|
|
|
5
5
|
|
|
6
6
|
require_relative "constants"
|
|
7
7
|
require_relative "http_client"
|
|
8
|
+
require_relative "replay"
|
|
8
9
|
require_relative "span_context"
|
|
9
10
|
require_relative "serialize"
|
|
10
11
|
|
|
@@ -23,6 +24,21 @@ module Simforge
|
|
|
23
24
|
@enabled = false
|
|
24
25
|
end
|
|
25
26
|
@http_client = HttpClient.new(api_key:, service_url: @service_url)
|
|
27
|
+
@pending_span_threads = {}
|
|
28
|
+
@pending_span_mutex = Mutex.new
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Replay historical traces through a method and create a test run.
|
|
32
|
+
#
|
|
33
|
+
# @param receiver [Object, Class] an instance for instance methods, or a Class for class methods
|
|
34
|
+
# @param method_name [Symbol] the method to replay
|
|
35
|
+
# @param trace_function_key [String] the trace function key for this method
|
|
36
|
+
# @param limit [Integer] maximum number of traces to replay (default: 5)
|
|
37
|
+
# @param trace_ids [Array<String>, nil] optional list of trace IDs to filter
|
|
38
|
+
# @param max_concurrency [Integer, nil] max threads for parallel replay (default: 10)
|
|
39
|
+
# @return [Hash] with :items, :test_run_id, :test_run_url
|
|
40
|
+
def replay(receiver, method_name, trace_function_key:, limit: 5, trace_ids: nil, max_concurrency: 10)
|
|
41
|
+
Replay.run(self, receiver, method_name, trace_function_key:, limit:, trace_ids:, max_concurrency:)
|
|
26
42
|
end
|
|
27
43
|
|
|
28
44
|
# Execute a block inside a span context, sending trace data on completion.
|
|
@@ -42,9 +58,14 @@ module Simforge
|
|
|
42
58
|
TraceState.create(trace_id)
|
|
43
59
|
end
|
|
44
60
|
|
|
61
|
+
if is_root_span
|
|
62
|
+
@pending_span_mutex.synchronize { @pending_span_threads[trace_id] = [] }
|
|
63
|
+
end
|
|
64
|
+
|
|
45
65
|
result = nil
|
|
46
66
|
error = nil
|
|
47
67
|
span_contexts = nil
|
|
68
|
+
span_prompt = nil
|
|
48
69
|
|
|
49
70
|
begin
|
|
50
71
|
SpanContext.with_span(trace_id:, span_id:) do
|
|
@@ -52,6 +73,7 @@ module Simforge
|
|
|
52
73
|
ensure
|
|
53
74
|
# Capture contexts before the span context is popped
|
|
54
75
|
span_contexts = SpanContext.current&.dig(:contexts)
|
|
76
|
+
span_prompt = SpanContext.current&.dig(:prompt)
|
|
55
77
|
end
|
|
56
78
|
rescue => e
|
|
57
79
|
error = e.message
|
|
@@ -61,7 +83,11 @@ module Simforge
|
|
|
61
83
|
begin
|
|
62
84
|
ended_at = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ")
|
|
63
85
|
|
|
64
|
-
|
|
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
|
+
span_thread = send_span(
|
|
65
91
|
trace_function_key:,
|
|
66
92
|
trace_id:,
|
|
67
93
|
span_id:,
|
|
@@ -70,22 +96,32 @@ module Simforge
|
|
|
70
96
|
span_type:,
|
|
71
97
|
function_name:,
|
|
72
98
|
contexts: span_contexts,
|
|
99
|
+
prompt: span_prompt,
|
|
73
100
|
args:,
|
|
74
101
|
kwargs:,
|
|
75
102
|
result:,
|
|
76
103
|
error:,
|
|
77
104
|
started_at:,
|
|
78
|
-
ended_at
|
|
105
|
+
ended_at:,
|
|
106
|
+
test_run_id: resolved_test_run_id,
|
|
107
|
+
input_source_span_id: resolved_input_source_span_id
|
|
79
108
|
)
|
|
80
109
|
|
|
81
|
-
# For root spans, also send trace completion
|
|
82
110
|
if is_root_span
|
|
111
|
+
pending = @pending_span_mutex.synchronize { @pending_span_threads.delete(trace_id) || [] }
|
|
112
|
+
pending << span_thread if span_thread
|
|
113
|
+
pending.each { |t| t.join(5) }
|
|
114
|
+
|
|
83
115
|
send_trace_completion(
|
|
84
116
|
trace_function_key:,
|
|
85
117
|
trace_id:,
|
|
86
118
|
started_at:,
|
|
87
119
|
ended_at:
|
|
88
120
|
)
|
|
121
|
+
else
|
|
122
|
+
@pending_span_mutex.synchronize do
|
|
123
|
+
@pending_span_threads[trace_id] << span_thread if span_thread && @pending_span_threads.key?(trace_id)
|
|
124
|
+
end
|
|
89
125
|
end
|
|
90
126
|
rescue Exception # rubocop:disable Lint/RescueException
|
|
91
127
|
# Silently ignore — user's result/exception takes priority
|
|
@@ -141,8 +177,8 @@ module Simforge
|
|
|
141
177
|
end
|
|
142
178
|
|
|
143
179
|
def send_span(trace_function_key:, trace_id:, span_id:, parent_span_id:,
|
|
144
|
-
span_name:, span_type:, function_name:, contexts:, args:, kwargs:, result:, error:,
|
|
145
|
-
started_at:, ended_at:)
|
|
180
|
+
span_name:, span_type:, function_name:, contexts:, prompt:, args:, kwargs:, result:, error:,
|
|
181
|
+
started_at:, ended_at:, test_run_id: nil, input_source_span_id: nil)
|
|
146
182
|
# Human-readable JSON (input/output fields)
|
|
147
183
|
human_inputs = Serialize.serialize_inputs(args, kwargs)
|
|
148
184
|
human_output = Serialize.serialize_value(result)
|
|
@@ -163,6 +199,7 @@ module Simforge
|
|
|
163
199
|
span_data["output_serialized"] = marshalled_output if marshalled_output
|
|
164
200
|
span_data["error"] = error if error
|
|
165
201
|
span_data["contexts"] = contexts if contexts&.any?
|
|
202
|
+
span_data["prompt"] = prompt if prompt
|
|
166
203
|
|
|
167
204
|
raw_span = {
|
|
168
205
|
"id" => span_id,
|
|
@@ -172,14 +209,18 @@ module Simforge
|
|
|
172
209
|
"span_data" => span_data
|
|
173
210
|
}
|
|
174
211
|
raw_span["parent_id"] = parent_span_id if parent_span_id
|
|
212
|
+
raw_span["input_source_span_id"] = input_source_span_id if input_source_span_id
|
|
175
213
|
|
|
176
|
-
|
|
214
|
+
payload = {
|
|
177
215
|
"type" => "sdk-function",
|
|
178
216
|
"source" => "ruby-sdk-function",
|
|
179
217
|
"sourceTraceId" => trace_id,
|
|
180
218
|
"traceFunctionKey" => trace_function_key,
|
|
181
219
|
"rawSpan" => raw_span
|
|
182
|
-
|
|
220
|
+
}
|
|
221
|
+
payload["testRunId"] = test_run_id if test_run_id
|
|
222
|
+
|
|
223
|
+
@http_client.send_external_span(payload) # Returns the background thread
|
|
183
224
|
end
|
|
184
225
|
end
|
|
185
226
|
end
|
data/lib/simforge/constants.rb
CHANGED
data/lib/simforge/http_client.rb
CHANGED
|
@@ -57,7 +57,8 @@ module Simforge
|
|
|
57
57
|
raise last_error
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
# Send an external span
|
|
60
|
+
# Send an external span in a background thread.
|
|
61
|
+
# Returns the thread for callers that need to await completion.
|
|
61
62
|
def send_external_span(payload)
|
|
62
63
|
merged = payload.merge("sdkVersion" => VERSION)
|
|
63
64
|
|
|
@@ -66,6 +67,57 @@ module Simforge
|
|
|
66
67
|
end
|
|
67
68
|
end
|
|
68
69
|
|
|
70
|
+
# Make a GET request to the Simforge API.
|
|
71
|
+
# Returns parsed JSON response hash.
|
|
72
|
+
def get(endpoint, timeout: nil)
|
|
73
|
+
uri = URI("#{@service_url}#{endpoint}")
|
|
74
|
+
request_timeout = timeout || @timeout
|
|
75
|
+
|
|
76
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
77
|
+
http.use_ssl = uri.scheme == "https"
|
|
78
|
+
http.open_timeout = request_timeout
|
|
79
|
+
http.read_timeout = request_timeout
|
|
80
|
+
|
|
81
|
+
req = Net::HTTP::Get.new(uri.path, headers)
|
|
82
|
+
response = http.request(req)
|
|
83
|
+
|
|
84
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
85
|
+
raise Net::HTTPError.new("HTTP #{response.code}: #{response.body}", response)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
result = JSON.parse(response.body)
|
|
89
|
+
|
|
90
|
+
if result["error"]
|
|
91
|
+
msg = result["error"]
|
|
92
|
+
msg = "#{msg} Configure it at: #{@service_url}#{result["url"]}" if result["url"]
|
|
93
|
+
raise StandardError, msg
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
result
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Start a replay session by fetching historical traces.
|
|
100
|
+
# Blocking call. Returns hash with testRunId, testRunUrl, and items array.
|
|
101
|
+
def start_replay(trace_function_key, limit, trace_ids: nil)
|
|
102
|
+
payload = {
|
|
103
|
+
"traceFunctionKey" => trace_function_key,
|
|
104
|
+
"limit" => limit
|
|
105
|
+
}
|
|
106
|
+
payload["traceIds"] = trace_ids if trace_ids
|
|
107
|
+
|
|
108
|
+
request("/api/sdk/replay/start", payload, timeout: 30)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Fetch an external span by ID. Blocking GET request.
|
|
112
|
+
def get_external_span(span_id)
|
|
113
|
+
get("/api/sdk/externalSpans/#{span_id}", timeout: 30)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Mark a replay test run as completed. Blocking call.
|
|
117
|
+
def complete_replay(test_run_id)
|
|
118
|
+
request("/api/sdk/replay/complete", {"testRunId" => test_run_id}, timeout: 30)
|
|
119
|
+
end
|
|
120
|
+
|
|
69
121
|
# Send an external trace (fire-and-forget in background thread).
|
|
70
122
|
def send_external_trace(payload)
|
|
71
123
|
merged = payload.merge("sdkVersion" => VERSION)
|
|
@@ -92,16 +144,22 @@ module Simforge
|
|
|
92
144
|
|
|
93
145
|
class << self
|
|
94
146
|
# Run a block in a background thread with tracking.
|
|
147
|
+
# Returns the thread for callers that need to join on it.
|
|
95
148
|
def _run_in_background(&block)
|
|
96
149
|
thread = Thread.new do
|
|
97
150
|
block.call
|
|
98
|
-
rescue
|
|
99
|
-
|
|
151
|
+
rescue => e
|
|
152
|
+
begin
|
|
153
|
+
warn "Simforge: Failed to send request: #{e.message}"
|
|
154
|
+
rescue
|
|
155
|
+
# Never crash the host app
|
|
156
|
+
end
|
|
100
157
|
ensure
|
|
101
158
|
@pending_threads_mutex.synchronize { @pending_threads.delete(Thread.current) }
|
|
102
159
|
end
|
|
103
160
|
|
|
104
161
|
@pending_threads_mutex.synchronize { @pending_threads << thread }
|
|
162
|
+
thread
|
|
105
163
|
end
|
|
106
164
|
|
|
107
165
|
# Wait for all pending background threads to complete.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "constants"
|
|
4
|
+
require_relative "serialize"
|
|
5
|
+
|
|
6
|
+
module Simforge
|
|
7
|
+
# Thread-local replay context management.
|
|
8
|
+
module ReplayContext
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def current
|
|
12
|
+
Thread.current[REPLAY_CONTEXT_KEY]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Execute a block with replay context set on the current thread.
|
|
16
|
+
# The context is automatically cleared when the block completes.
|
|
17
|
+
def with_context(test_run_id:, input_source_span_id: nil)
|
|
18
|
+
previous = Thread.current[REPLAY_CONTEXT_KEY]
|
|
19
|
+
Thread.current[REPLAY_CONTEXT_KEY] = {
|
|
20
|
+
test_run_id:,
|
|
21
|
+
input_source_span_id:
|
|
22
|
+
}
|
|
23
|
+
yield
|
|
24
|
+
ensure
|
|
25
|
+
Thread.current[REPLAY_CONTEXT_KEY] = previous
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Replay historical traces through a traced method and create a test run.
|
|
30
|
+
module Replay
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
# Replay historical traces through a method and create a test run.
|
|
34
|
+
#
|
|
35
|
+
# Fetches the last N traces for the given trace function key, re-runs each
|
|
36
|
+
# through the provided receiver and method, and returns comparison data.
|
|
37
|
+
#
|
|
38
|
+
# @param client [Simforge::Client] the Simforge client instance
|
|
39
|
+
# @param receiver [Object, Class] an instance for instance methods, or a Class for class methods
|
|
40
|
+
# @param method_name [Symbol] the method to replay
|
|
41
|
+
# @param trace_function_key [String] the trace function key for this method
|
|
42
|
+
# @param limit [Integer] maximum number of traces to replay (default: 5)
|
|
43
|
+
# @param trace_ids [Array<String>, nil] optional list of trace IDs to filter
|
|
44
|
+
# @param max_concurrency [Integer, nil] max threads for parallel replay (default: 10)
|
|
45
|
+
# @return [Hash] with :items, :test_run_id, :test_run_url
|
|
46
|
+
def run(client, receiver, method_name, trace_function_key:, limit: 5, trace_ids: nil, max_concurrency: 10)
|
|
47
|
+
http_client = client.instance_variable_get(:@http_client)
|
|
48
|
+
|
|
49
|
+
replay_data = http_client.start_replay(trace_function_key, limit, trace_ids:)
|
|
50
|
+
test_run_id = replay_data["testRunId"]
|
|
51
|
+
test_run_url = replay_data["testRunUrl"]
|
|
52
|
+
server_items = replay_data["items"] || []
|
|
53
|
+
|
|
54
|
+
result_items = if server_items.any?
|
|
55
|
+
process_items(http_client, server_items, receiver, method_name, test_run_id, max_concurrency)
|
|
56
|
+
else
|
|
57
|
+
[]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
Simforge.flush_traces
|
|
61
|
+
|
|
62
|
+
begin
|
|
63
|
+
http_client.complete_replay(test_run_id)
|
|
64
|
+
rescue => e
|
|
65
|
+
warn "Simforge: Failed to complete replay: #{e.message}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
{
|
|
69
|
+
items: result_items,
|
|
70
|
+
test_run_id:,
|
|
71
|
+
test_run_url: "#{client.service_url}#{test_run_url}"
|
|
72
|
+
}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Process all replay items, optionally in parallel using threads.
|
|
76
|
+
def process_items(http_client, server_items, receiver, method_name, test_run_id, max_concurrency)
|
|
77
|
+
concurrency = max_concurrency || server_items.length
|
|
78
|
+
|
|
79
|
+
if concurrency <= 1
|
|
80
|
+
server_items.map { |item| process_single_item(http_client, item, receiver, method_name, test_run_id) }
|
|
81
|
+
else
|
|
82
|
+
results_mutex = Mutex.new
|
|
83
|
+
results = []
|
|
84
|
+
work_queue = server_items.each_with_index.to_a
|
|
85
|
+
work_mutex = Mutex.new
|
|
86
|
+
|
|
87
|
+
workers = [concurrency, server_items.length].min.times.map do
|
|
88
|
+
Thread.new do
|
|
89
|
+
loop do
|
|
90
|
+
item, idx = work_mutex.synchronize { work_queue.shift }
|
|
91
|
+
break unless item
|
|
92
|
+
|
|
93
|
+
result = process_single_item(http_client, item, receiver, method_name, test_run_id)
|
|
94
|
+
results_mutex.synchronize { results[idx] = result }
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
workers.each(&:join)
|
|
100
|
+
results.compact
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Fetch span data and execute a single replay item.
|
|
105
|
+
def process_single_item(http_client, server_item, receiver, method_name, test_run_id)
|
|
106
|
+
span = http_client.get_external_span(server_item["externalSpanId"])
|
|
107
|
+
item_data = extract_span_data(span)
|
|
108
|
+
execute_item(item_data, receiver, method_name, test_run_id, span["id"])
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Extract input/output data from an external span's rawData.
|
|
112
|
+
def extract_span_data(span)
|
|
113
|
+
raw_data = span["rawData"] || {}
|
|
114
|
+
span_data = raw_data["span_data"] || {}
|
|
115
|
+
|
|
116
|
+
{
|
|
117
|
+
"input" => span_data["input"],
|
|
118
|
+
"output" => span_data["output"],
|
|
119
|
+
"inputSerialized" => span_data["input_serialized"],
|
|
120
|
+
"outputSerialized" => span_data["output_serialized"]
|
|
121
|
+
}
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# 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)
|
|
126
|
+
args, kwargs = Serialize.deserialize_inputs(item)
|
|
127
|
+
|
|
128
|
+
fn_result = nil
|
|
129
|
+
fn_error = nil
|
|
130
|
+
|
|
131
|
+
ReplayContext.with_context(test_run_id:, input_source_span_id:) do
|
|
132
|
+
fn_result = if kwargs.empty?
|
|
133
|
+
receiver.send(method_name, *args)
|
|
134
|
+
else
|
|
135
|
+
receiver.send(method_name, *args, **kwargs)
|
|
136
|
+
end
|
|
137
|
+
rescue => e
|
|
138
|
+
fn_error = e.message
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
{
|
|
142
|
+
input: args,
|
|
143
|
+
result: fn_result,
|
|
144
|
+
original_output: item["output"],
|
|
145
|
+
error: fn_error
|
|
146
|
+
}
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
data/lib/simforge/serialize.rb
CHANGED
|
@@ -65,5 +65,39 @@ module Simforge
|
|
|
65
65
|
def unmarshal_value(encoded)
|
|
66
66
|
Marshal.load(Base64.strict_decode64(encoded)) # rubocop:disable Security/MarshalLoad
|
|
67
67
|
end
|
|
68
|
+
|
|
69
|
+
# Deserialize replay inputs from a span's data into [args, kwargs].
|
|
70
|
+
#
|
|
71
|
+
# Prefers Marshal-serialized `inputSerialized` for type preservation,
|
|
72
|
+
# falls back to the raw `input` field.
|
|
73
|
+
#
|
|
74
|
+
# @param item [Hash] with "inputSerialized" and/or "input" keys
|
|
75
|
+
# @return [Array(Array, Hash)] positional args and keyword args
|
|
76
|
+
def deserialize_inputs(item)
|
|
77
|
+
input_serialized = item["inputSerialized"]
|
|
78
|
+
raw_input = item["input"]
|
|
79
|
+
|
|
80
|
+
if input_serialized.is_a?(String) && !input_serialized.empty?
|
|
81
|
+
begin
|
|
82
|
+
deserialized = unmarshal_value(input_serialized)
|
|
83
|
+
if deserialized.is_a?(Hash) && (deserialized.key?(:args) || deserialized.key?(:kwargs))
|
|
84
|
+
return [deserialized[:args] || [], deserialized[:kwargs] || {}]
|
|
85
|
+
end
|
|
86
|
+
return deserialized.nil? ? [[], {}] : [[deserialized], {}]
|
|
87
|
+
rescue
|
|
88
|
+
# Fall through to raw_input
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if raw_input.is_a?(Array)
|
|
93
|
+
[raw_input, {}]
|
|
94
|
+
elsif raw_input.is_a?(Hash)
|
|
95
|
+
[[], raw_input.transform_keys(&:to_sym)]
|
|
96
|
+
elsif raw_input.nil?
|
|
97
|
+
[[], {}]
|
|
98
|
+
else
|
|
99
|
+
[[raw_input], {}]
|
|
100
|
+
end
|
|
101
|
+
end
|
|
68
102
|
end
|
|
69
103
|
end
|
|
@@ -25,6 +25,19 @@ module Simforge
|
|
|
25
25
|
rescue
|
|
26
26
|
# Silently ignore - never crash the host app
|
|
27
27
|
end
|
|
28
|
+
|
|
29
|
+
# Set the prompt for this span.
|
|
30
|
+
# The prompt is stored in span_data.prompt. Calling multiple times
|
|
31
|
+
# overwrites the previous value.
|
|
32
|
+
#
|
|
33
|
+
# @param prompt [String] the prompt string to store
|
|
34
|
+
def set_prompt(prompt)
|
|
35
|
+
return unless prompt.is_a?(String)
|
|
36
|
+
|
|
37
|
+
@span_state[:prompt] = prompt
|
|
38
|
+
rescue
|
|
39
|
+
# Silently ignore - never crash the host app
|
|
40
|
+
end
|
|
28
41
|
end
|
|
29
42
|
|
|
30
43
|
# Handle to the current active trace, allowing trace-level context to be set.
|
|
@@ -84,7 +97,7 @@ module Simforge
|
|
|
84
97
|
# Thread-local span stack for tracking nested spans.
|
|
85
98
|
# Each entry is a Hash with :trace_id and :span_id keys.
|
|
86
99
|
module SpanContext
|
|
87
|
-
STACK_KEY = :
|
|
100
|
+
STACK_KEY = :__simforge_span_stack
|
|
88
101
|
|
|
89
102
|
module_function
|
|
90
103
|
|
data/lib/simforge/version.rb
CHANGED
data/lib/simforge.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative "simforge/constants"
|
|
|
5
5
|
require_relative "simforge/serialize"
|
|
6
6
|
require_relative "simforge/span_context"
|
|
7
7
|
require_relative "simforge/http_client"
|
|
8
|
+
require_relative "simforge/replay"
|
|
8
9
|
require_relative "simforge/client"
|
|
9
10
|
require_relative "simforge/traceable"
|
|
10
11
|
|
|
@@ -19,6 +20,10 @@ module Simforge
|
|
|
19
20
|
def add_context(_context)
|
|
20
21
|
# No-op
|
|
21
22
|
end
|
|
23
|
+
|
|
24
|
+
def set_prompt(_prompt)
|
|
25
|
+
# No-op
|
|
26
|
+
end
|
|
22
27
|
end
|
|
23
28
|
|
|
24
29
|
# No-op trace handle returned when outside a span context.
|
metadata
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: simforge
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Harvest Team
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
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'
|
|
13
26
|
- !ruby/object:Gem::Dependency
|
|
14
27
|
name: rake
|
|
15
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -107,6 +120,7 @@ files:
|
|
|
107
120
|
- lib/simforge/client.rb
|
|
108
121
|
- lib/simforge/constants.rb
|
|
109
122
|
- lib/simforge/http_client.rb
|
|
123
|
+
- lib/simforge/replay.rb
|
|
110
124
|
- lib/simforge/serialize.rb
|
|
111
125
|
- lib/simforge/span_context.rb
|
|
112
126
|
- lib/simforge/traceable.rb
|
|
@@ -118,7 +132,6 @@ metadata:
|
|
|
118
132
|
homepage_uri: https://simforge.goharvest.ai
|
|
119
133
|
source_code_uri: https://simforge.goharvest.ai
|
|
120
134
|
rubygems_mfa_required: 'true'
|
|
121
|
-
post_install_message:
|
|
122
135
|
rdoc_options: []
|
|
123
136
|
require_paths:
|
|
124
137
|
- lib
|
|
@@ -126,15 +139,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
126
139
|
requirements:
|
|
127
140
|
- - ">="
|
|
128
141
|
- !ruby/object:Gem::Version
|
|
129
|
-
version: '3.
|
|
142
|
+
version: '3.4'
|
|
130
143
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
131
144
|
requirements:
|
|
132
145
|
- - ">="
|
|
133
146
|
- !ruby/object:Gem::Version
|
|
134
147
|
version: '0'
|
|
135
148
|
requirements: []
|
|
136
|
-
rubygems_version: 3.
|
|
137
|
-
signing_key:
|
|
149
|
+
rubygems_version: 3.6.9
|
|
138
150
|
specification_version: 4
|
|
139
151
|
summary: Simforge Ruby SDK for function tracing and span management
|
|
140
152
|
test_files: []
|