nitro_intelligence 3.0.2 → 3.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: feb0fe8d0b4ae0d1da8564f048fa7130dc78318622a23a86163edc65d2f37dbf
4
- data.tar.gz: 122277bfc6b70b0aeb67166624458f37f50b1ed2b1a77044ade87575ea101bb1
3
+ metadata.gz: 2151383fb66e0292ac5fbf73f16697e32b9c0f7d64263500babdb505275925cc
4
+ data.tar.gz: 91b5f4bbbb3e87f56e883b710f8af8a6c1900e3c1e733d3eba14258bbc8ebf05
5
5
  SHA512:
6
- metadata.gz: bf69e4777017afa0be7457612c42aadf434921c083b3a199e13a0682959b6579494793069e2548c592dd022ac5a96d342155549cfa5d36fdf798bd0a05bd647f
7
- data.tar.gz: 18854e89e04d994bddc0315296d5f21ad2551dd9b7d64d01efda6aadc25dbcd0858d1e8f24dacb2f2b261b2b65ab1bb3fe747956f5064ce6ef176b017e59b712
6
+ metadata.gz: d058b2fed129d9c85210558afe41b947a44090a5532aca084a6eff04fa2913f4e0d284f0848818ce98195673833e6d675141ee06464bafcf588cfdf29bfea9f8
7
+ data.tar.gz: 10b6c3213894be249388a5bdbc7cc5ef963b904581d00de2d02ac3175aeb9abc9348e959a2f54c8a27b20cb19b1ecdd4c09fdb974325d331367d25df98be73b7
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
+ require "rubocop/rake_task"
3
4
 
4
5
  RSpec::Core::RakeTask.new(:spec)
6
+ RuboCop::RakeTask.new
5
7
 
6
- task default: :spec
8
+ task default: %i[spec rubocop]
data/docs/README.md CHANGED
@@ -323,7 +323,7 @@ client.chat(
323
323
 
324
324
  ### Custom Trace IDs
325
325
 
326
- To generate a deterministic trace ID in the observability platform, you can pass `trace_seed` as a parameter. This is useful when you want a stable identifier that is derived from a specific domain value (e.g., document ID). The same `trace_seed` will produce the same trace ID, making it easier to correlate multiple events.
326
+ To generate a deterministic trace ID in the observability platform, you can pass `trace_seed` as a parameter. This is useful when you want a stable identifier that is derived from a specific domain value (e.g., document ID). The same `trace_seed` will produce the same trace ID, making it easier to correlate multiple events. Each seeded call creates a root observation in that trace, including when another observation is active. With Langfuse v4 event reads, trace input and output come from the most recently ingested root observation. Calls without a seed keep their active parent observation.
327
327
 
328
328
  ```ruby
329
329
  document_id = "document-123"
@@ -424,6 +424,16 @@ trace_id = NitroIntelligence::Trace.create_id(seed: document_id)
424
424
  reporter.score(name: "precision", value: 0.5, trace_id:)
425
425
  ```
426
426
 
427
+ #### Using different data types for scoring
428
+
429
+ By default, scores submitted on a trace are numeric, but you can use the `data_type` keyword to change it to `:boolean` or `:categorical`. For example:
430
+
431
+ ```ruby
432
+ reporter.score(name: "precision", value: "Partially Correct", trace_id:, data_type: :categorical)
433
+ ```
434
+
435
+ Numeric data is helpful for scores that happen on a sliding scale, like success rate or percent similarity. Boolean data is helpful to convey something that either passes or fails. Categorical data is helpful for scores that have pre-defined categories, like "Correct", "Partially Correct", and "Incorrect".
436
+
427
437
  ### Prompts
428
438
 
429
439
  #### Message Shape
@@ -10,6 +10,10 @@ module NitroIntelligence
10
10
 
11
11
  MAX_STATUS_MESSAGE_LENGTH = 2000
12
12
 
13
+ # Attributes a handler may report off a response, each written only when it supplied
14
+ # one. See #record_result for why absence is left as absence.
15
+ RECORDED_ATTRIBUTES = %i[model model_parameters usage_details cost_details input output].freeze
16
+
13
17
  attr_reader :project_client
14
18
 
15
19
  def initialize(project_client:)
@@ -63,15 +67,30 @@ module NitroIntelligence
63
67
 
64
68
  handle_truncation(trace_attributes[:input], trace_attributes[:output], trace_attributes[:model])
65
69
 
66
- generation.model = trace_attributes[:model] if trace_attributes[:model]
67
- generation.usage_details = trace_attributes[:usage_details] if trace_attributes[:usage_details]
68
- generation.cost_details = trace_attributes[:cost_details] if trace_attributes[:cost_details]
69
- generation.input = trace_attributes[:input] if trace_attributes[:input]
70
- generation.output = trace_attributes[:output] if trace_attributes[:output]
70
+ RECORDED_ATTRIBUTES.each do |name|
71
+ value = trace_attributes[name]
72
+ generation.public_send(:"#{name}=", value) if value
73
+ end
74
+
75
+ record_status(generation, trace_attributes)
71
76
 
72
77
  generation.update_trace(input: trace_attributes[:input], output: trace_attributes[:output])
73
78
  end
74
79
 
80
+ # A response the endpoint answered but did not finish -- a generation cut off at its
81
+ # token ceiling, say -- is neither a success worth nothing being said about nor a
82
+ # failure the request raised on. Recording it at WARNING leaves it findable without
83
+ # putting it among the errors.
84
+ def record_status(generation, trace_attributes)
85
+ status_message = trace_attributes[:status_message]
86
+ return if status_message.blank?
87
+
88
+ generation.update(
89
+ level: trace_attributes[:level] || "WARNING",
90
+ status_message: status_message.truncate(MAX_STATUS_MESSAGE_LENGTH)
91
+ )
92
+ end
93
+
75
94
  # Recorded before the request is made so that a request which raises still
76
95
  # shows what was sent. Handlers whose input is not safe to record twice
77
96
  # (image generation sends base64 payloads that are replaced with media
@@ -4,6 +4,9 @@
4
4
  # The content of this file should eventually make its way upstream. Setting a custom trace ID is
5
5
  # already awaiting approval in https://github.com/simplepractice/langfuse-rb/pull/69.
6
6
  #
7
+ # Explicit trace IDs create real roots here while the upstream fix awaits approval:
8
+ # https://github.com/simplepractice/langfuse-rb/pull/128.
9
+ #
7
10
 
8
11
  require "nitro_intelligence/langfuse_tracer_provider"
9
12
 
@@ -50,11 +53,15 @@ module NitroIntelligence
50
53
  Langfuse.send(:validate_observation_type!, as_type, type_str) unless skip_validation
51
54
 
52
55
  otel_tracer = @tracer_provider.tracer
53
- otel_span = Langfuse.send(:create_otel_span,
54
- name:,
55
- start_time:,
56
- parent_span_context:,
57
- otel_tracer:)
56
+ otel_span = if trace_id
57
+ @tracer_provider.start_root_span(name, trace_id: parent_span_context.trace_id, start_time:)
58
+ else
59
+ Langfuse.send(:create_otel_span,
60
+ name:,
61
+ start_time:,
62
+ parent_span_context:,
63
+ otel_tracer:)
64
+ end
58
65
 
59
66
  # Serialize attributes
60
67
  # Only set attributes if span is still recording (should always be true here, but guard for safety)
@@ -7,6 +7,7 @@
7
7
  module NitroIntelligence
8
8
  class LangfuseTracerProvider
9
9
  def initialize(config)
10
+ @trace_id_key = OpenTelemetry::Context.create_key("nitro-intelligence-trace-id")
10
11
  @tracer_provider = create_tracer_provider(config)
11
12
  end
12
13
 
@@ -18,6 +19,25 @@ module NitroIntelligence
18
19
  @tracer_provider.shutdown(timeout:)
19
20
  end
20
21
 
22
+ def start_root_span(name, trace_id:, start_time: nil)
23
+ # A seed identifies the trace, not a missing parent. Preserve propagated
24
+ # attributes while clearing the ambient span for this explicit root.
25
+ context = OpenTelemetry::Trace.context_with_span(OpenTelemetry::Trace::Span::INVALID)
26
+ context = context.set_value(@trace_id_key, trace_id)
27
+ # generate_trace_id has no context argument, so expose the seed through Context.current.
28
+ OpenTelemetry::Context.with_current(context) do
29
+ tracer.start_span(name, with_parent: context, start_timestamp: start_time)
30
+ end
31
+ end
32
+
33
+ def generate_trace_id
34
+ OpenTelemetry::Context.value(@trace_id_key) || OpenTelemetry::Trace.generate_trace_id
35
+ end
36
+
37
+ def generate_span_id
38
+ OpenTelemetry::Trace.generate_span_id
39
+ end
40
+
21
41
  private
22
42
 
23
43
  def create_tracer_provider(config)
@@ -36,7 +56,7 @@ module NitroIntelligence
36
56
  max_export_batch_size: config.batch_size
37
57
  )
38
58
 
39
- tracer_provider = OpenTelemetry::SDK::Trace::TracerProvider.new
59
+ tracer_provider = OpenTelemetry::SDK::Trace::TracerProvider.new(id_generator: self)
40
60
  tracer_provider.add_span_processor(processor)
41
61
  span_processor = Langfuse::SpanProcessor.new(config:)
42
62
  tracer_provider.add_span_processor(span_processor)
@@ -53,6 +73,7 @@ module NitroIntelligence
53
73
  encoded = Base64.strict_encode64(credentials)
54
74
  {
55
75
  "Authorization" => "Basic #{encoded}",
76
+ "x-langfuse-ingestion-version" => "4",
56
77
  }
57
78
  end
58
79
  end
@@ -35,12 +35,13 @@ module NitroIntelligence
35
35
  response
36
36
  end
37
37
 
38
- def score(trace_id:, name:, value:, id: "#{trace_id}-#{name}")
38
+ def score(trace_id:, name:, value:, id: "#{trace_id}-#{name}", data_type: :numeric)
39
39
  @project_client.observability_client.create_score(
40
40
  id:,
41
41
  trace_id:,
42
42
  name:,
43
43
  value:,
44
+ data_type:,
44
45
  environment: NitroIntelligence.environment
45
46
  )
46
47
  end
@@ -1,3 +1,3 @@
1
1
  module NitroIntelligence
2
- VERSION = "3.0.2".freeze
2
+ VERSION = "3.1.0".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nitro_intelligence
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Igor Artemenko