Module: Langfuse::OtelAttributes

Defined in:
lib/langfuse/otel_attributes.rb

Overview

Serialization layer that converts Langfuse domain models to OpenTelemetry span attributes format

This module provides methods to convert user-friendly Langfuse attribute objects into the internal OpenTelemetry attribute format required by the span processor.

rubocop:disable Metrics/ModuleLength

Examples:

Converting trace attributes

attrs = Langfuse::Types::TraceAttributes.new(
  name: "user-checkout-flow",
  user_id: "user-123",
  tags: ["checkout", "payment"],
  metadata: { version: "2.1.0" }
)
otel_attrs = Langfuse::OtelAttributes.create_trace_attributes(attrs)
span.set_attributes(otel_attrs)

Converting observation attributes

attrs = Langfuse::Types::GenerationAttributes.new(
  model: "gpt-4",
  input: { messages: [...] },
  usage_details: { prompt_tokens: 100 }
)
otel_attrs = Langfuse::OtelAttributes.create_observation_attributes("generation", attrs)
span.set_attributes(otel_attrs)

Constant Summary collapse

TRACE_NAME =

Trace attributes

"langfuse.trace.name"
TRACE_USER_ID =

TRACE_USER_ID and TRACE_SESSION_ID are without langfuse prefix because they follow OpenTelemetry semantic conventions

"user.id"
TRACE_SESSION_ID =
"session.id"
TRACE_INPUT =
"langfuse.trace.input"
TRACE_OUTPUT =
"langfuse.trace.output"
TRACE_METADATA =
"langfuse.trace.metadata"
TRACE_TAGS =
"langfuse.trace.tags"
TRACE_PUBLIC =
"langfuse.trace.public"
OBSERVATION_TYPE =

Observation attributes

"langfuse.observation.type"
OBSERVATION_INPUT =
"langfuse.observation.input"
OBSERVATION_OUTPUT =
"langfuse.observation.output"
OBSERVATION_METADATA =
"langfuse.observation.metadata"
OBSERVATION_LEVEL =
"langfuse.observation.level"
OBSERVATION_STATUS_MESSAGE =
"langfuse.observation.status_message"
OBSERVATION_MODEL =
"langfuse.observation.model.name"
OBSERVATION_MODEL_PARAMETERS =
"langfuse.observation.model.parameters"
OBSERVATION_USAGE_DETAILS =
"langfuse.observation.usage_details"
OBSERVATION_COST_DETAILS =
"langfuse.observation.cost_details"
OBSERVATION_PROMPT_NAME =
"langfuse.observation.prompt.name"
OBSERVATION_PROMPT_VERSION =
"langfuse.observation.prompt.version"
OBSERVATION_COMPLETION_START_TIME =
"langfuse.observation.completion_start_time"
VERSION =

Common attributes

"langfuse.version"
RELEASE =
"langfuse.release"
ENVIRONMENT =
"langfuse.environment"

Class Method Summary collapse

Class Method Details

.add_prompt_attributes(otel_attributes, prompt) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Adds prompt attributes if prompt is present and not a fallback

rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

Parameters:

  • otel_attributes (Hash)

    Attributes hash to modify

  • prompt (Hash, Object, nil)

    Prompt hash or object



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/langfuse/otel_attributes.rb', line 258

def self.add_prompt_attributes(otel_attributes, prompt)
  return unless prompt

  # Handle hash-like prompts
  if prompt.is_a?(Hash) || prompt.respond_to?(:[])
    return if prompt[:is_fallback] || prompt["is_fallback"]

    otel_attributes[OBSERVATION_PROMPT_NAME] = prompt[:name] || prompt["name"]
    otel_attributes[OBSERVATION_PROMPT_VERSION] = prompt[:version] || prompt["version"]
  # Handle objects with name/version methods (already converted in Trace#generation)
  elsif prompt.respond_to?(:name) && prompt.respond_to?(:version)
    otel_attributes[OBSERVATION_PROMPT_NAME] = prompt.name
    otel_attributes[OBSERVATION_PROMPT_VERSION] = prompt.version
  end
end

.build_observation_base_attributes(type, get_value) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Builds base observation attributes (without prompt)

Parameters:

  • type (String)

    Observation type

  • get_value (Proc)

    Lambda to get values from attributes hash

Returns:

  • (Hash)

    Base observation attributes



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/langfuse/otel_attributes.rb', line 233

def self.build_observation_base_attributes(type, get_value)
  {
    OBSERVATION_TYPE => type,
    OBSERVATION_LEVEL => get_value.call(:level),
    OBSERVATION_STATUS_MESSAGE => get_value.call(:status_message),
    VERSION => get_value.call(:version),
    OBSERVATION_INPUT => serialize(get_value.call(:input)),
    OBSERVATION_OUTPUT => serialize(get_value.call(:output)),
    OBSERVATION_MODEL => get_value.call(:model),
    OBSERVATION_USAGE_DETAILS => serialize(get_value.call(:usage_details)),
    OBSERVATION_COST_DETAILS => serialize(get_value.call(:cost_details)),
    OBSERVATION_COMPLETION_START_TIME => serialize(get_value.call(:completion_start_time)),
    OBSERVATION_MODEL_PARAMETERS => serialize(get_value.call(:model_parameters)),
    ENVIRONMENT => get_value.call(:environment),
    **(get_value.call(:metadata), OBSERVATION_METADATA)
  }
end

.create_observation_attributes(type, attrs) ⇒ Hash

Creates OpenTelemetry attributes from Langfuse observation attributes

Converts user-friendly observation attributes into the internal OpenTelemetry attribute format required by the span processor.

Examples:

attrs = Langfuse::Types::GenerationAttributes.new(
  model: "gpt-4",
  input: { messages: [...] },
  usage_details: { prompt_tokens: 100 }
)
otel_attrs = Langfuse::OtelAttributes.create_observation_attributes("generation", attrs)

Parameters:

Returns:

  • (Hash)

    OpenTelemetry attributes hash with non-nil values



120
121
122
123
124
125
126
127
128
129
# File 'lib/langfuse/otel_attributes.rb', line 120

def self.create_observation_attributes(type, attrs)
  attrs = attrs.to_h
  get_value = ->(key) { get_hash_value(attrs, key) }

  otel_attributes = build_observation_base_attributes(type, get_value)
  add_prompt_attributes(otel_attributes, get_value.call(:prompt))

  # Remove nil values
  otel_attributes.compact
end

.create_trace_attributes(attrs) ⇒ Hash

Creates OpenTelemetry attributes from Langfuse trace attributes

Converts user-friendly trace attributes into the internal OpenTelemetry attribute format required by the span processor.

Examples:

attrs = Langfuse::Types::TraceAttributes.new(
  name: "user-checkout-flow",
  user_id: "user-123",
  session_id: "session-456",
  tags: ["checkout", "payment"],
  metadata: { version: "2.1.0" }
)
otel_attrs = Langfuse::OtelAttributes.create_trace_attributes(attrs)

Parameters:

Returns:

  • (Hash)

    OpenTelemetry attributes hash with non-nil values



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/langfuse/otel_attributes.rb', line 80

def self.create_trace_attributes(attrs)
  # Convert to hash if it's a TraceAttributes object
  attrs = attrs.to_h
  get_value = ->(key) { get_hash_value(attrs, key) }

  attributes = {
    TRACE_NAME => get_value.call(:name),
    TRACE_USER_ID => get_value.call(:user_id),
    TRACE_SESSION_ID => get_value.call(:session_id),
    VERSION => get_value.call(:version),
    RELEASE => get_value.call(:release),
    TRACE_INPUT => serialize(get_value.call(:input)),
    TRACE_OUTPUT => serialize(get_value.call(:output)),
    TRACE_TAGS => serialize(get_value.call(:tags)),
    ENVIRONMENT => get_value.call(:environment),
    TRACE_PUBLIC => get_value.call(:public),
    **(get_value.call(:metadata), TRACE_METADATA)
  }

  # Remove nil values
  attributes.compact
end

.flatten_hash_value(value, key) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Flattens a single hash value (recursively if it’s a hash, serializes otherwise)

Parameters:

  • value (Object)

    Value to flatten

  • key (String)

    Attribute key prefix

Returns:

  • (Hash)

    Flattened attributes hash



199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/langfuse/otel_attributes.rb', line 199

def self.flatten_hash_value(value, key)
  if value.is_a?(Hash)
    # Recursively flatten nested hashes
    (value, key)
  elsif value.is_a?(Array)
    # Serialize arrays to JSON
    serialized = serialize(value, preserve_strings: true)
    serialized ? { key => serialized } : {}
  else
    # Convert simple values (strings, numbers, booleans) to strings
    { key => value.to_s }
  end
end

.flatten_metadata(metadata, prefix) ⇒ Hash

Flattens and serializes metadata into OpenTelemetry attribute format

Converts nested metadata objects into dot-notation attribute keys. For example, ‘{ database: { host: ’localhost’ } }‘ becomes `{ ’langfuse.trace.metadata.database.host’: ‘localhost’ }‘.

Examples:

({ user: { id: 123 } }, "langfuse.trace.metadata")
# => { "langfuse.trace.metadata.user.id" => "123" }

Parameters:

  • metadata (Hash, Array, Object, nil)

    Metadata to flatten

  • prefix (String)

    Prefix for attribute keys (e.g., “langfuse.trace.metadata”)

Returns:

  • (Hash)

    Flattened metadata attributes



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/langfuse/otel_attributes.rb', line 172

def self.(, prefix)
  return {} if .nil?

  # Handle non-hash metadata (arrays, primitives, etc.)
  unless .is_a?(Hash)
    serialized = serialize(, preserve_strings: true)
    return serialized ? { prefix => serialized } : {}
  end

  # Recursively flatten hash metadata
  result = {}
  .each do |key, value|
    next if value.nil?

    new_key = "#{prefix}.#{key}"
    result.merge!(flatten_hash_value(value, new_key))
  end

  result
end

.get_hash_value(hash, key) ⇒ Object?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Gets a value from a hash supporting both symbol and string keys Handles false values correctly (doesn’t treat false as nil)

Parameters:

  • hash (Hash)

    Hash to get value from

  • key (Symbol, String)

    Key to look up

Returns:

  • (Object, nil)

    Value from hash or nil



220
221
222
223
224
225
# File 'lib/langfuse/otel_attributes.rb', line 220

def self.get_hash_value(hash, key)
  return hash[key] if hash.key?(key)
  return hash[key.to_s] if hash.key?(key.to_s)

  nil
end

.serialize(obj, preserve_strings: false) ⇒ String?

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Safely serializes an object to JSON string

Examples:

Always JSON-serialize (default)

serialize({ key: "value" }) # => '{"key":"value"}'
serialize("string") # => '"string"'
serialize(nil) # => nil

Preserve strings

serialize("already a string", preserve_strings: true) # => "already a string"
serialize([1, 2, 3], preserve_strings: true) # => "[1,2,3]"

Parameters:

  • obj (Object, nil)

    Object to serialize

  • preserve_strings (Boolean) (defaults to: false)

    If true, preserves strings as-is; if false, JSON-serializes everything including strings

Returns:

  • (String, nil)

    JSON string, original string (if preserve_strings is true), or nil if nil/undefined



147
148
149
150
151
152
153
154
155
156
# File 'lib/langfuse/otel_attributes.rb', line 147

def self.serialize(obj, preserve_strings: false)
  return nil if obj.nil?
  return obj if preserve_strings && obj.is_a?(String)

  begin
    obj.to_json
  rescue StandardError
    nil
  end
end