diff --git a/app/models/provider/openai.rb b/app/models/provider/openai.rb index 6ec10333d..154b2d02e 100644 --- a/app/models/provider/openai.rb +++ b/app/models/provider/openai.rb @@ -82,7 +82,7 @@ class Provider::Openai < Provider json_mode: json_mode ).auto_categorize - trace&.update(output: result.map(&:to_h)) + upsert_langfuse_trace(trace: trace, output: result.map(&:to_h)) result end @@ -110,7 +110,7 @@ class Provider::Openai < Provider json_mode: json_mode ).auto_detect_merchants - trace&.update(output: result.map(&:to_h)) + upsert_langfuse_trace(trace: trace, output: result.map(&:to_h)) result end @@ -147,7 +147,7 @@ class Provider::Openai < Provider family: family ).process - trace&.update(output: result.to_h) + upsert_langfuse_trace(trace: trace, output: result.to_h) result end @@ -168,7 +168,7 @@ class Provider::Openai < Provider model: effective_model ).extract - trace&.update(output: { transaction_count: result[:transactions].size }) + upsert_langfuse_trace(trace: trace, output: { transaction_count: result[:transactions].size }) result end @@ -480,7 +480,7 @@ class Provider::Openai < Provider environment: Rails.env ) rescue => e - Rails.logger.warn("Langfuse trace creation failed: #{e.message}") + Rails.logger.warn("Langfuse trace creation failed: #{e.message}\n#{e.full_message}") nil end @@ -505,16 +505,32 @@ class Provider::Openai < Provider output: { error: error.message, details: error.respond_to?(:details) ? error.details : nil }, level: "ERROR" ) - trace&.update( + upsert_langfuse_trace( + trace: trace, output: { error: error.message }, level: "ERROR" ) else generation&.end(output: output, usage: usage) - trace&.update(output: output) + upsert_langfuse_trace(trace: trace, output: output) end rescue => e - Rails.logger.warn("Langfuse logging failed: #{e.message}") + Rails.logger.warn("Langfuse logging failed: #{e.message}\n#{e.full_message}") + end + + def upsert_langfuse_trace(trace:, output:, level: nil) + return unless langfuse_client && trace&.id + + payload = { + id: trace.id, + output: output + } + payload[:level] = level if level.present? + + langfuse_client.trace(**payload) + rescue => e + Rails.logger.warn("Langfuse trace upsert failed for trace_id=#{trace&.id}: #{e.message}\n#{e.full_message}") + nil end def record_llm_usage(family:, model:, operation:, usage: nil, error: nil) diff --git a/test/models/provider/openai_test.rb b/test/models/provider/openai_test.rb index 83b2b787e..879fe20e7 100644 --- a/test/models/provider/openai_test.rb +++ b/test/models/provider/openai_test.rb @@ -286,4 +286,48 @@ class Provider::OpenaiTest < ActiveSupport::TestCase assert_equal "configured model: custom-model", custom_provider.supported_models_description end + + test "upsert_langfuse_trace uses client trace upsert" do + trace = Struct.new(:id).new("trace_123") + fake_client = mock + + fake_client.expects(:trace).with(id: "trace_123", output: { ok: true }, level: "ERROR") + @subject.stubs(:langfuse_client).returns(fake_client) + + @subject.send(:upsert_langfuse_trace, trace: trace, output: { ok: true }, level: "ERROR") + end + + test "log_langfuse_generation upserts trace through client" do + trace = Struct.new(:id).new("trace_456") + generation = mock + fake_client = mock + + @subject.stubs(:langfuse_client).returns(fake_client) + @subject.stubs(:create_langfuse_trace).returns(trace) + + fake_client.expects(:trace).with(id: "trace_456", output: "hello") + trace.expects(:generation).returns(generation) + generation.expects(:end).with(output: "hello", usage: { "total_tokens" => 10 }) + + @subject.send( + :log_langfuse_generation, + name: "chat", + model: "gpt-4.1", + input: { prompt: "Hi" }, + output: "hello", + usage: { "total_tokens" => 10 } + ) + end + + test "create_langfuse_trace logs full error details" do + fake_client = mock + error = StandardError.new("boom") + + @subject.stubs(:langfuse_client).returns(fake_client) + fake_client.expects(:trace).raises(error) + + Rails.logger.expects(:warn).with(regexp_matches(/Langfuse trace creation failed: boom.*test\/models\/provider\/openai_test\.rb/m)) + + @subject.send(:create_langfuse_trace, name: "openai.test", input: { foo: "bar" }) + end end