mirror of
https://github.com/we-promise/sure.git
synced 2026-09-05 14:51:15 +00:00
* fix(providers): capture API error response body in PDF processor span output Anthropic and OpenAI PDF processing errors only logged the exception message, dropping the parsed response body that usually explains the failure. Add safe_error_body to both providers' UsageRecorder concerns and include it in the langfuse span output on failure, with tests. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013SYp89xEYfTkrxw8HcCUQ8 * fix(providers): allowlist PDF processor error fields sent to Langfuse safe_error_body forwarded the entire upstream error body into the langfuse span output. For custom OpenAI-compatible providers/proxies (and the analogous Anthropic path), that body can echo request content from the financial document being processed. Replace it with safe_error_detail, which extracts only type/message/code/request_id instead of the raw body. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013SYp89xEYfTkrxw8HcCUQ8 * test(openai): cover request_id extraction in PDF processor error_detail The safe_error_detail request_id path (error.response_headers) had no test coverage. Stub response_headers with x-request-id and assert it appears in error_detail. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013SYp89xEYfTkrxw8HcCUQ8 --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
86 lines
2.3 KiB
Ruby
86 lines
2.3 KiB
Ruby
require "test_helper"
|
|
|
|
class Provider::Openai::PdfProcessorTest < ActiveSupport::TestCase
|
|
setup do
|
|
@pdf_content = "%PDF-1.4 fake bytes".b
|
|
end
|
|
|
|
test "extracts only allowlisted error fields into span output when the API call fails" do
|
|
error = StandardError.new("boom")
|
|
def error.response_body
|
|
{
|
|
"error" => { "type" => "invalid_request_error", "message" => "invalid request", "code" => "bad_pdf" },
|
|
"request" => { "messages" => "statement text that should never leak" }
|
|
}
|
|
end
|
|
def error.response_headers
|
|
{ "x-request-id" => "req_abc123" }
|
|
end
|
|
|
|
captured_output = nil
|
|
trace = stub_trace { |output| captured_output = output }
|
|
|
|
assert_raises(StandardError) do
|
|
build_processor(error, trace).process
|
|
end
|
|
|
|
assert_equal(
|
|
{ type: "invalid_request_error", message: "invalid request", code: "bad_pdf", request_id: "req_abc123" },
|
|
captured_output[:error_detail]
|
|
)
|
|
end
|
|
|
|
test "error_detail is nil in span output when the error exposes no response_body" do
|
|
error = StandardError.new("boom")
|
|
|
|
captured_output = nil
|
|
trace = stub_trace { |output| captured_output = output }
|
|
|
|
assert_raises(StandardError) do
|
|
build_processor(error, trace).process
|
|
end
|
|
|
|
assert_nil captured_output[:error_detail]
|
|
end
|
|
|
|
test "error_detail falls back to a placeholder when reading response_body itself raises" do
|
|
error = StandardError.new("boom")
|
|
def error.response_body
|
|
raise "response_body accessor exploded"
|
|
end
|
|
|
|
captured_output = nil
|
|
trace = stub_trace { |output| captured_output = output }
|
|
|
|
assert_raises(StandardError) do
|
|
build_processor(error, trace).process
|
|
end
|
|
|
|
assert_match(/detail unavailable/i, captured_output[:error_detail])
|
|
end
|
|
|
|
private
|
|
def build_processor(error, trace)
|
|
client = mock
|
|
client.expects(:chat).raises(error)
|
|
|
|
processor = Provider::Openai::PdfProcessor.new(
|
|
client,
|
|
model: "gpt-4.1",
|
|
pdf_content: @pdf_content,
|
|
langfuse_trace: trace,
|
|
max_response_tokens: 1000
|
|
)
|
|
processor.stubs(:extract_text_from_pdf).returns("Statement text")
|
|
processor
|
|
end
|
|
|
|
def stub_trace
|
|
span = mock
|
|
span.expects(:end).with { |args| yield(args[:output]); true }
|
|
trace = mock
|
|
trace.stubs(:span).returns(span)
|
|
trace
|
|
end
|
|
end
|