diff --git a/lib/langfuse.rb b/lib/langfuse.rb index 6697dc8..bcbb3a6 100644 --- a/lib/langfuse.rb +++ b/lib/langfuse.rb @@ -190,10 +190,13 @@ def propagate_attributes(user_id: nil, session_id: nil, metadata: nil, version: # # @param name [String] Score name (required) # @param value [Numeric, Integer, String] Score value (type depends on data_type) + # @param id [String, nil] Score ID # @param trace_id [String, nil] Trace ID to associate with the score + # @param session_id [String, nil] Session ID to associate with the score # @param observation_id [String, nil] Observation ID to associate with the score # @param comment [String, nil] Optional comment # @param metadata [Hash, nil] Optional metadata hash + # @param environment [String, nil] Optional environment # @param data_type [Symbol] Data type (:numeric, :boolean, :categorical) # @return [void] # @raise [ArgumentError] if validation fails @@ -207,15 +210,18 @@ def propagate_attributes(user_id: nil, session_id: nil, metadata: nil, version: # @example Categorical score # Langfuse.create_score(name: "category", value: "high", trace_id: "abc123", data_type: :categorical) # rubocop:disable Metrics/ParameterLists - def create_score(name:, value:, trace_id: nil, observation_id: nil, comment: nil, metadata: nil, - data_type: :numeric) + def create_score(name:, value:, id: nil, trace_id: nil, session_id: nil, observation_id: nil, comment: nil, + metadata: nil, environment: nil, data_type: :numeric) client.create_score( name: name, value: value, + id: id, trace_id: trace_id, + session_id: session_id, observation_id: observation_id, comment: comment, metadata: metadata, + environment: environment, data_type: data_type ) end diff --git a/lib/langfuse/client.rb b/lib/langfuse/client.rb index fc201e7..b879f43 100644 --- a/lib/langfuse/client.rb +++ b/lib/langfuse/client.rb @@ -283,12 +283,17 @@ def dataset_run_url(dataset_id:, dataset_run_id:) # Create a score event and queue it for batching # + # You may only provide one of the following: trace_id (with optional observation_id), session_id, or dataset_run_id; observation_id requires a trace_id. + # # @param name [String] Score name (required) # @param value [Numeric, Integer, String] Score value (type depends on data_type) + # @param id [String, nil] Score ID # @param trace_id [String, nil] Trace ID to associate with the score + # @param session_id [String, nil] Session ID to associate with the score # @param observation_id [String, nil] Observation ID to associate with the score # @param comment [String, nil] Optional comment # @param metadata [Hash, nil] Optional metadata hash + # @param environment [String, nil] Optional environment # @param data_type [Symbol] Data type (:numeric, :boolean, :categorical) # @param dataset_run_id [String, nil] Optional dataset run ID to associate with the score # @param config_id [String, nil] Optional score config ID @@ -304,15 +309,18 @@ def dataset_run_url(dataset_id:, dataset_run_id:) # @example Categorical score # client.create_score(name: "category", value: "high", trace_id: "abc123", data_type: :categorical) # rubocop:disable Metrics/ParameterLists - def create_score(name:, value:, trace_id: nil, observation_id: nil, comment: nil, metadata: nil, - data_type: :numeric, dataset_run_id: nil, config_id: nil) + def create_score(name:, value:, id: nil, trace_id: nil, session_id: nil, observation_id: nil, comment: nil, + metadata: nil, environment: nil, data_type: :numeric, dataset_run_id: nil, config_id: nil) @score_client.create( name: name, value: value, + id: id, trace_id: trace_id, + session_id: session_id, observation_id: observation_id, comment: comment, metadata: metadata, + environment: environment, data_type: data_type, dataset_run_id: dataset_run_id, config_id: config_id diff --git a/lib/langfuse/score_client.rb b/lib/langfuse/score_client.rb index b69fee9..8dccfb6 100644 --- a/lib/langfuse/score_client.rb +++ b/lib/langfuse/score_client.rb @@ -51,10 +51,13 @@ def initialize(api_client:, config:) # # @param name [String] Score name (required) # @param value [Numeric, Integer, String] Score value (type depends on data_type) + # @param id [String, nil] Score ID # @param trace_id [String, nil] Trace ID to associate with the score + # @param session_id [String, nil] Session ID to associate with the score # @param observation_id [String, nil] Observation ID to associate with the score # @param comment [String, nil] Optional comment # @param metadata [Hash, nil] Optional metadata hash + # @param environment [String, nil] Optional environment # @param data_type [Symbol] Data type (:numeric, :boolean, :categorical) # @param dataset_run_id [String, nil] Optional dataset run ID to associate with the score # @param config_id [String, nil] Optional score config ID @@ -70,8 +73,8 @@ def initialize(api_client:, config:) # @example Categorical score # create(name: "category", value: "high", trace_id: "abc123", data_type: :categorical) # rubocop:disable Metrics/ParameterLists - def create(name:, value:, trace_id: nil, observation_id: nil, comment: nil, metadata: nil, - data_type: :numeric, dataset_run_id: nil, config_id: nil) + def create(name:, value:, id: nil, trace_id: nil, session_id: nil, observation_id: nil, comment: nil, + metadata: nil, environment: nil, data_type: :numeric, dataset_run_id: nil, config_id: nil) validate_name(name) normalized_value = normalize_value(value, data_type) data_type_str = Types::SCORE_DATA_TYPES[data_type] || raise(ArgumentError, "Invalid data_type: #{data_type}") @@ -79,10 +82,13 @@ def create(name:, value:, trace_id: nil, observation_id: nil, comment: nil, meta event = build_score_event( name: name, value: normalized_value, + id: id, trace_id: trace_id, + session_id: session_id, observation_id: observation_id, comment: comment, metadata: metadata, + environment: environment, data_type: data_type_str, dataset_run_id: dataset_run_id, config_id: config_id @@ -204,25 +210,30 @@ def shutdown # # @param name [String] Score name # @param value [Object] Normalized score value + # @param id [String, nil] Score ID # @param trace_id [String, nil] Trace ID + # @param session_id [String, nil] Session ID # @param observation_id [String, nil] Observation ID # @param comment [String, nil] Comment # @param metadata [Hash, nil] Metadata + # @param environment [String, nil] Environment # @param data_type [String] Data type string (NUMERIC, BOOLEAN, CATEGORICAL) # @return [Hash] Event hash - # rubocop:disable Metrics/ParameterLists - def build_score_event(name:, value:, trace_id:, observation_id:, comment:, metadata:, data_type:, - dataset_run_id: nil, config_id: nil) + # rubocop:disable Metrics/ParameterLists, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity + def build_score_event(name:, value:, id:, trace_id:, session_id:, observation_id:, comment:, metadata:, + environment:, data_type:, dataset_run_id: nil, config_id: nil) body = { - id: SecureRandom.uuid, + id: id || SecureRandom.uuid, name: name, value: value, dataType: data_type } body[:traceId] = trace_id if trace_id + body[:sessionId] = session_id if session_id body[:observationId] = observation_id if observation_id body[:comment] = comment if comment body[:metadata] = metadata if metadata + body[:environment] = environment if environment body[:datasetRunId] = dataset_run_id if dataset_run_id body[:configId] = config_id if config_id @@ -233,7 +244,7 @@ def build_score_event(name:, value:, trace_id:, observation_id:, comment:, metad body: body } end - # rubocop:enable Metrics/ParameterLists + # rubocop:enable Metrics/ParameterLists, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity # Normalize and validate score value based on data type # diff --git a/spec/langfuse/client_spec.rb b/spec/langfuse/client_spec.rb index 10ebba4..3637d7a 100644 --- a/spec/langfuse/client_spec.rb +++ b/spec/langfuse/client_spec.rb @@ -1434,10 +1434,13 @@ class << self expect(score_client).to receive(:create).with( name: "quality", value: 0.85, + id: nil, trace_id: "abc123", + session_id: nil, observation_id: nil, comment: nil, metadata: nil, + environment: nil, data_type: :numeric, dataset_run_id: nil, config_id: nil @@ -1446,15 +1449,18 @@ class << self client.create_score(name: "quality", value: 0.85, trace_id: "abc123") end - it "passes all parameters to score_client" do + it "passes all parameters to score_client, identified by trace_id" do score_client = client.instance_variable_get(:@score_client) expect(score_client).to receive(:create).with( name: "quality", value: 0.85, + id: "my-score", trace_id: "abc123", + session_id: nil, observation_id: "def456", comment: "High quality", metadata: { source: "manual" }, + environment: "production", data_type: :boolean, dataset_run_id: nil, config_id: nil @@ -1463,10 +1469,41 @@ class << self client.create_score( name: "quality", value: 0.85, + id: "my-score", trace_id: "abc123", observation_id: "def456", comment: "High quality", metadata: { source: "manual" }, + environment: "production", + data_type: :boolean + ) + end + + it "passes all parameters to score_client, identified by session_id" do + score_client = client.instance_variable_get(:@score_client) + expect(score_client).to receive(:create).with( + name: "quality", + value: 0.85, + id: "my-score", + trace_id: nil, + session_id: "ghi789", + observation_id: nil, + comment: "High quality", + metadata: { source: "manual" }, + environment: "production", + data_type: :boolean, + dataset_run_id: nil, + config_id: nil + ) + + client.create_score( + name: "quality", + value: 0.85, + id: "my-score", + session_id: "ghi789", + comment: "High quality", + metadata: { source: "manual" }, + environment: "production", data_type: :boolean ) end diff --git a/spec/langfuse/score_client_spec.rb b/spec/langfuse/score_client_spec.rb index 4528445..f82c46d 100644 --- a/spec/langfuse/score_client_spec.rb +++ b/spec/langfuse/score_client_spec.rb @@ -62,6 +62,17 @@ score_client.flush end + it "includes id when provided" do + expect(api_client).to receive(:send_batch).with(array_including( + hash_including( + body: hash_including(id: "my-score") + ) + )) + + score_client.create(name: "quality", value: 0.85, id: "my-score") + score_client.flush + end + it "includes trace_id when provided" do expect(api_client).to receive(:send_batch).with(array_including( hash_including( @@ -73,6 +84,17 @@ score_client.flush end + it "includes session_id when provided" do + expect(api_client).to receive(:send_batch).with(array_including( + hash_including( + body: hash_including(sessionId: "ghi789") + ) + )) + + score_client.create(name: "quality", value: 0.85, session_id: "ghi789") + score_client.flush + end + it "includes observation_id when provided" do expect(api_client).to receive(:send_batch).with(array_including( hash_including( @@ -84,12 +106,13 @@ score_client.flush end - it "includes comment and metadata when provided" do + it "includes comment and metadata and environment when provided" do expect(api_client).to receive(:send_batch).with(array_including( hash_including( body: hash_including( comment: "High quality", - metadata: { source: "manual" } + metadata: { source: "manual" }, + environment: "production" ) ) )) @@ -98,7 +121,8 @@ name: "quality", value: 0.85, comment: "High quality", - metadata: { source: "manual" } + metadata: { source: "manual" }, + environment: "production" ) score_client.flush end