braintrust 0.0.10 → 0.0.12
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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a4dd86789a3ce80b891b88fb9b125bb7b6d85e86928adeb5fd7c1fec671ff56
|
|
4
|
+
data.tar.gz: 171fe031f960d0a6f3abbfacbb20094b35bcd9199cf132e30063868807f95f8b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3cf52e457ca5d4cdad2f8873bb45d0eeb0b79b33c5edbcd467e65ce7261caea9bcc86ea9d562835197279105f14f2787bcac6622faad4ec420a5f2fa27a003d2
|
|
7
|
+
data.tar.gz: 5a7ccc20a3e63840e15c41dd82eaa8653a2f0dce693321e6527ace59c54255524ae1d794d2dce058127b747a514e8777729384cde9c157374039880bb3b01013
|
data/lib/braintrust/config.rb
CHANGED
|
@@ -39,7 +39,7 @@ module Braintrust
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
new(
|
|
42
|
-
api_key: api_key || ENV["BRAINTRUST_API_KEY"],
|
|
42
|
+
api_key: api_key || ((ENV["BRAINTRUST_API_KEY"] && ENV["BRAINTRUST_API_KEY"].empty?) ? nil : ENV["BRAINTRUST_API_KEY"]),
|
|
43
43
|
org_name: org_name || ENV["BRAINTRUST_ORG_NAME"],
|
|
44
44
|
default_project: default_project || ENV["BRAINTRUST_DEFAULT_PROJECT"],
|
|
45
45
|
app_url: app_url || ENV["BRAINTRUST_APP_URL"] || "https://www.braintrust.dev",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Braintrust
|
|
4
|
+
module Internal
|
|
5
|
+
# Encoding utilities using Ruby's native pack/unpack methods.
|
|
6
|
+
# Avoids dependency on external gems that became bundled gems in Ruby 3.4.
|
|
7
|
+
module Encoding
|
|
8
|
+
# Base64 encoding/decoding using Ruby's native pack/unpack methods.
|
|
9
|
+
# Drop-in replacement for the base64 gem's strict methods.
|
|
10
|
+
#
|
|
11
|
+
# @example Encode binary data
|
|
12
|
+
# Encoding::Base64.strict_encode64(image_bytes)
|
|
13
|
+
# # => "iVBORw0KGgo..."
|
|
14
|
+
#
|
|
15
|
+
# @example Decode base64 string
|
|
16
|
+
# Encoding::Base64.strict_decode64("iVBORw0KGgo...")
|
|
17
|
+
# # => "\x89PNG..."
|
|
18
|
+
#
|
|
19
|
+
module Base64
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# Encodes binary data to base64 without newlines (strict encoding).
|
|
23
|
+
#
|
|
24
|
+
# @param data [String] Binary data to encode
|
|
25
|
+
# @return [String] Base64-encoded string without newlines
|
|
26
|
+
def strict_encode64(data)
|
|
27
|
+
[data].pack("m0")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Decodes a base64 string to binary data (strict decoding).
|
|
31
|
+
#
|
|
32
|
+
# @param str [String] Base64-encoded string
|
|
33
|
+
# @return [String] Decoded binary data
|
|
34
|
+
def strict_decode64(str)
|
|
35
|
+
str.unpack1("m0")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "base64"
|
|
4
3
|
require "net/http"
|
|
4
|
+
require_relative "../internal/encoding"
|
|
5
5
|
require "uri"
|
|
6
6
|
|
|
7
7
|
module Braintrust
|
|
@@ -110,7 +110,7 @@ module Braintrust
|
|
|
110
110
|
# att.to_data_url
|
|
111
111
|
# # => "data:image/png;base64,iVBORw0KGgo..."
|
|
112
112
|
def to_data_url
|
|
113
|
-
encoded = Base64.strict_encode64(@data)
|
|
113
|
+
encoded = Internal::Encoding::Base64.strict_encode64(@data)
|
|
114
114
|
"data:#{@content_type};base64,#{encoded}"
|
|
115
115
|
end
|
|
116
116
|
|
|
@@ -4,6 +4,7 @@ require "opentelemetry/sdk"
|
|
|
4
4
|
require "json"
|
|
5
5
|
require_relative "../../../tokens"
|
|
6
6
|
require_relative "../../../../logger"
|
|
7
|
+
require_relative "../../../../internal/encoding"
|
|
7
8
|
|
|
8
9
|
module Braintrust
|
|
9
10
|
module Trace
|
|
@@ -350,10 +351,15 @@ module Braintrust
|
|
|
350
351
|
if tool_schema && tool_schema[function_key]
|
|
351
352
|
tool_params = tool_schema[function_key][:parameters] || tool_schema[function_key]["parameters"]
|
|
352
353
|
if tool_params.is_a?(Hash)
|
|
354
|
+
# Create a mutable copy if the hash is frozen
|
|
355
|
+
tool_params = tool_params.dup if tool_params.frozen?
|
|
353
356
|
tool_params.delete("strict")
|
|
354
357
|
tool_params.delete(:strict)
|
|
355
358
|
tool_params.delete("additionalProperties")
|
|
356
359
|
tool_params.delete(:additionalProperties)
|
|
360
|
+
# Assign the modified copy back
|
|
361
|
+
params_key = tool_schema[function_key].key?(:parameters) ? :parameters : "parameters"
|
|
362
|
+
tool_schema[function_key][params_key] = tool_params
|
|
357
363
|
end
|
|
358
364
|
end
|
|
359
365
|
|
|
@@ -417,18 +423,14 @@ module Braintrust
|
|
|
417
423
|
|
|
418
424
|
# Handle content
|
|
419
425
|
if msg.respond_to?(:content) && msg.content
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
if
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
rescue
|
|
428
|
-
# Keep original if conversion fails
|
|
429
|
-
end
|
|
426
|
+
raw_content = msg.content
|
|
427
|
+
|
|
428
|
+
# Check if content is a Content object with attachments (issue #71)
|
|
429
|
+
formatted["content"] = if raw_content.respond_to?(:text) && raw_content.respond_to?(:attachments) && raw_content.attachments&.any?
|
|
430
|
+
format_multipart_content(raw_content)
|
|
431
|
+
else
|
|
432
|
+
format_simple_content(raw_content, msg.role.to_s)
|
|
430
433
|
end
|
|
431
|
-
formatted["content"] = content
|
|
432
434
|
end
|
|
433
435
|
|
|
434
436
|
# Handle tool_calls for assistant messages
|
|
@@ -445,6 +447,74 @@ module Braintrust
|
|
|
445
447
|
formatted
|
|
446
448
|
end
|
|
447
449
|
|
|
450
|
+
# Format multipart content with text and attachments
|
|
451
|
+
# @param content_obj [Object] Content object with text and attachments
|
|
452
|
+
# @return [Array<Hash>] array of content parts
|
|
453
|
+
def self.format_multipart_content(content_obj)
|
|
454
|
+
content_parts = []
|
|
455
|
+
|
|
456
|
+
# Add text part
|
|
457
|
+
content_parts << {"type" => "text", "text" => content_obj.text} if content_obj.text
|
|
458
|
+
|
|
459
|
+
# Add attachment parts (convert to Braintrust format)
|
|
460
|
+
content_obj.attachments.each do |attachment|
|
|
461
|
+
content_parts << format_attachment_for_input(attachment)
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
content_parts
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# Format simple text content
|
|
468
|
+
# @param raw_content [Object] String or Content object with text
|
|
469
|
+
# @param role [String] the message role
|
|
470
|
+
# @return [String] formatted text content
|
|
471
|
+
def self.format_simple_content(raw_content, role)
|
|
472
|
+
content = raw_content
|
|
473
|
+
content = content.text if content.respond_to?(:text)
|
|
474
|
+
|
|
475
|
+
# Convert Ruby hash string to JSON for tool results
|
|
476
|
+
if role == "tool" && content.is_a?(String) && content.start_with?("{:")
|
|
477
|
+
begin
|
|
478
|
+
content = content.gsub(/(?<=\{|, ):(\w+)=>/, '"\1":').gsub("=>", ":")
|
|
479
|
+
rescue
|
|
480
|
+
# Keep original if conversion fails
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
content
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
# Format a RubyLLM attachment to OpenAI-compatible format
|
|
488
|
+
# @param attachment [Object] the RubyLLM attachment
|
|
489
|
+
# @return [Hash] OpenAI image_url format for consistency with other integrations
|
|
490
|
+
def self.format_attachment_for_input(attachment)
|
|
491
|
+
# RubyLLM Attachment has: source (Pathname), filename, mime_type
|
|
492
|
+
if attachment.respond_to?(:source) && attachment.source
|
|
493
|
+
begin
|
|
494
|
+
data = File.binread(attachment.source.to_s)
|
|
495
|
+
encoded = Internal::Encoding::Base64.strict_encode64(data)
|
|
496
|
+
mime_type = attachment.respond_to?(:mime_type) ? attachment.mime_type : "application/octet-stream"
|
|
497
|
+
|
|
498
|
+
# Use OpenAI's image_url format for consistency
|
|
499
|
+
{
|
|
500
|
+
"type" => "image_url",
|
|
501
|
+
"image_url" => {
|
|
502
|
+
"url" => "data:#{mime_type};base64,#{encoded}"
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
rescue => e
|
|
506
|
+
Log.debug("Failed to read attachment file: #{e.message}")
|
|
507
|
+
# Return a placeholder if we can't read the file
|
|
508
|
+
{"type" => "text", "text" => "[attachment: #{attachment.respond_to?(:filename) ? attachment.filename : "unknown"}]"}
|
|
509
|
+
end
|
|
510
|
+
elsif attachment.respond_to?(:to_h)
|
|
511
|
+
# Try to use attachment's own serialization
|
|
512
|
+
attachment.to_h
|
|
513
|
+
else
|
|
514
|
+
{"type" => "text", "text" => "[attachment]"}
|
|
515
|
+
end
|
|
516
|
+
end
|
|
517
|
+
|
|
448
518
|
# Capture streaming output and metrics
|
|
449
519
|
# @param span [OpenTelemetry::Trace::Span] the span
|
|
450
520
|
# @param aggregated_chunks [Array] the aggregated chunks
|
|
@@ -453,8 +523,11 @@ module Braintrust
|
|
|
453
523
|
return if aggregated_chunks.empty?
|
|
454
524
|
|
|
455
525
|
# Aggregate content from chunks
|
|
526
|
+
# Extract text from Content objects if present (issue #71)
|
|
456
527
|
aggregated_content = aggregated_chunks.map { |c|
|
|
457
|
-
c.respond_to?(:content) ? c.content : c.to_s
|
|
528
|
+
content = c.respond_to?(:content) ? c.content : c.to_s
|
|
529
|
+
content = content.text if content.respond_to?(:text)
|
|
530
|
+
content
|
|
458
531
|
}.join
|
|
459
532
|
|
|
460
533
|
output = [{
|
|
@@ -485,8 +558,11 @@ module Braintrust
|
|
|
485
558
|
}
|
|
486
559
|
|
|
487
560
|
# Add content if it's a simple text response
|
|
561
|
+
# Extract text from Content objects if present (issue #71)
|
|
488
562
|
if response.respond_to?(:content) && response.content && !response.content.empty?
|
|
489
|
-
|
|
563
|
+
content = response.content
|
|
564
|
+
content = content.text if content.respond_to?(:text)
|
|
565
|
+
message["content"] = content
|
|
490
566
|
end
|
|
491
567
|
|
|
492
568
|
# Check if there are tool calls in the messages history
|
data/lib/braintrust/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: braintrust
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.12
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Braintrust
|
|
@@ -201,6 +201,7 @@ files:
|
|
|
201
201
|
- lib/braintrust/eval/runner.rb
|
|
202
202
|
- lib/braintrust/eval/scorer.rb
|
|
203
203
|
- lib/braintrust/eval/summary.rb
|
|
204
|
+
- lib/braintrust/internal/encoding.rb
|
|
204
205
|
- lib/braintrust/internal/experiments.rb
|
|
205
206
|
- lib/braintrust/internal/thread_pool.rb
|
|
206
207
|
- lib/braintrust/logger.rb
|