easy_talk 3.2.0 → 3.3.1

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +15 -43
  3. data/CHANGELOG.md +105 -0
  4. data/README.md +510 -2018
  5. data/docs/json_schema_compliance.md +140 -26
  6. data/docs/primitive-schema-rfc.md +894 -0
  7. data/examples/ruby_llm/Gemfile +12 -0
  8. data/examples/ruby_llm/structured_output.rb +47 -0
  9. data/examples/ruby_llm/tools_integration.rb +49 -0
  10. data/lib/easy_talk/builders/base_builder.rb +2 -1
  11. data/lib/easy_talk/builders/boolean_builder.rb +2 -1
  12. data/lib/easy_talk/builders/collection_helpers.rb +4 -0
  13. data/lib/easy_talk/builders/composition_builder.rb +7 -2
  14. data/lib/easy_talk/builders/integer_builder.rb +2 -1
  15. data/lib/easy_talk/builders/null_builder.rb +4 -1
  16. data/lib/easy_talk/builders/number_builder.rb +4 -1
  17. data/lib/easy_talk/builders/object_builder.rb +64 -3
  18. data/lib/easy_talk/builders/registry.rb +15 -1
  19. data/lib/easy_talk/builders/string_builder.rb +3 -1
  20. data/lib/easy_talk/builders/temporal_builder.rb +7 -0
  21. data/lib/easy_talk/builders/tuple_builder.rb +89 -0
  22. data/lib/easy_talk/builders/typed_array_builder.rb +4 -2
  23. data/lib/easy_talk/builders/union_builder.rb +5 -1
  24. data/lib/easy_talk/configuration.rb +17 -2
  25. data/lib/easy_talk/errors.rb +1 -0
  26. data/lib/easy_talk/errors_helper.rb +3 -0
  27. data/lib/easy_talk/extensions/ruby_llm_compatibility.rb +58 -0
  28. data/lib/easy_talk/json_schema_equality.rb +46 -0
  29. data/lib/easy_talk/keywords.rb +0 -1
  30. data/lib/easy_talk/model.rb +42 -1
  31. data/lib/easy_talk/model_helper.rb +4 -0
  32. data/lib/easy_talk/naming_strategies.rb +4 -0
  33. data/lib/easy_talk/property.rb +7 -0
  34. data/lib/easy_talk/ref_helper.rb +6 -0
  35. data/lib/easy_talk/schema.rb +1 -0
  36. data/lib/easy_talk/schema_definition.rb +52 -6
  37. data/lib/easy_talk/schema_methods.rb +36 -5
  38. data/lib/easy_talk/sorbet_extension.rb +1 -0
  39. data/lib/easy_talk/type_introspection.rb +45 -1
  40. data/lib/easy_talk/types/tuple.rb +77 -0
  41. data/lib/easy_talk/validation_adapters/active_model_adapter.rb +350 -62
  42. data/lib/easy_talk/validation_adapters/active_model_schema_validation.rb +106 -0
  43. data/lib/easy_talk/validation_adapters/base.rb +12 -0
  44. data/lib/easy_talk/validation_adapters/none_adapter.rb +9 -0
  45. data/lib/easy_talk/validation_builder.rb +1 -0
  46. data/lib/easy_talk/version.rb +1 -1
  47. data/lib/easy_talk.rb +1 -0
  48. metadata +17 -4
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ # typed: true
2
3
 
3
4
  require 'bigdecimal'
4
5
 
@@ -31,6 +32,8 @@ module EasyTalk
31
32
  }.freeze
32
33
 
33
34
  class << self
35
+ extend T::Sig
36
+
34
37
  # Check if type represents a boolean (T::Boolean or TrueClass/FalseClass).
35
38
  #
36
39
  # @param type [Object] The type to check
@@ -41,6 +44,7 @@ module EasyTalk
41
44
  # boolean_type?(TrueClass) # => true
42
45
  # boolean_type?(FalseClass) # => true
43
46
  # boolean_type?(String) # => false
47
+ sig { params(type: T.untyped).returns(T::Boolean) }
44
48
  def boolean_type?(type)
45
49
  return false if type.nil?
46
50
  return true if [TrueClass, FalseClass].include?(type)
@@ -58,6 +62,23 @@ module EasyTalk
58
62
  false
59
63
  end
60
64
 
65
+ # Check if a resolved type class represents a boolean union ([TrueClass, FalseClass]).
66
+ #
67
+ # This is useful when checking resolved type classes rather than raw Sorbet types.
68
+ # The internal representation of T::Boolean resolves to [TrueClass, FalseClass].
69
+ #
70
+ # @param type_class [Object] The resolved type class to check
71
+ # @return [Boolean] true if the type class is a boolean union array
72
+ #
73
+ # @example
74
+ # boolean_union_type?([TrueClass, FalseClass]) # => true
75
+ # boolean_union_type?(TrueClass) # => false
76
+ # boolean_union_type?(String) # => false
77
+ sig { params(type_class: T.untyped).returns(T::Boolean) }
78
+ def boolean_union_type?(type_class)
79
+ type_class.is_a?(Array) && type_class.sort_by(&:name) == [FalseClass, TrueClass].sort_by(&:name)
80
+ end
81
+
61
82
  # Check if type is a typed array (T::Array[...]).
62
83
  #
63
84
  # @param type [Object] The type to check
@@ -66,12 +87,30 @@ module EasyTalk
66
87
  # @example
67
88
  # typed_array?(T::Array[String]) # => true
68
89
  # typed_array?(Array) # => false
90
+ sig { params(type: T.untyped).returns(T::Boolean) }
69
91
  def typed_array?(type)
70
92
  return false if type.nil?
71
93
 
72
94
  type.is_a?(T::Types::TypedArray)
73
95
  end
74
96
 
97
+ # Check if type is any array type (plain Array, T::Array[...], or T::Tuple[...]).
98
+ #
99
+ # @param type [Object] The type to check
100
+ # @return [Boolean] true if the type is an array type
101
+ #
102
+ # @example
103
+ # array_type?(Array) # => true
104
+ # array_type?(T::Array[String]) # => true
105
+ # array_type?(T::Tuple[String, Integer]) # => true
106
+ # array_type?(String) # => false
107
+ sig { params(type: T.untyped).returns(T::Boolean) }
108
+ def array_type?(type)
109
+ return false if type.nil?
110
+
111
+ type == Array || type.is_a?(T::Types::TypedArray) || type.is_a?(EasyTalk::Types::Tuple)
112
+ end
113
+
75
114
  # Check if type is nilable (T.nilable(...)).
76
115
  #
77
116
  # @param type [Object] The type to check
@@ -80,6 +119,7 @@ module EasyTalk
80
119
  # @example
81
120
  # nilable_type?(T.nilable(String)) # => true
82
121
  # nilable_type?(String) # => false
122
+ sig { params(type: T.untyped).returns(T::Boolean) }
83
123
  def nilable_type?(type)
84
124
  return false if type.nil?
85
125
 
@@ -90,6 +130,7 @@ module EasyTalk
90
130
  #
91
131
  # @param type [Object] The type to check
92
132
  # @return [Boolean] true if the type is a primitive
133
+ sig { params(type: T.untyped).returns(T::Boolean) }
93
134
  def primitive_type?(type)
94
135
  return false if type.nil?
95
136
 
@@ -111,6 +152,7 @@ module EasyTalk
111
152
  # json_schema_type(Float) # => 'number'
112
153
  # json_schema_type(BigDecimal) # => 'number'
113
154
  # json_schema_type(String) # => 'string'
155
+ sig { params(type: T.untyped).returns(String) }
114
156
  def json_schema_type(type)
115
157
  return 'object' if type.nil?
116
158
  return 'boolean' if boolean_type?(type)
@@ -133,11 +175,12 @@ module EasyTalk
133
175
  # get_type_class(String) # => String
134
176
  # get_type_class(T::Boolean) # => [TrueClass, FalseClass]
135
177
  # get_type_class(T::Array[String]) # => Array
178
+ sig { params(type: T.untyped).returns(T.untyped) }
136
179
  def get_type_class(type)
137
180
  return nil if type.nil?
138
181
  return type if type.is_a?(Class)
139
182
  return type.raw_type if type.respond_to?(:raw_type)
140
- return Array if typed_array?(type)
183
+ return Array if type.is_a?(T::Types::TypedArray) || type.is_a?(EasyTalk::Types::Tuple)
141
184
  return [TrueClass, FalseClass] if boolean_type?(type)
142
185
 
143
186
  if nilable_type?(type)
@@ -155,6 +198,7 @@ module EasyTalk
155
198
  #
156
199
  # @example
157
200
  # extract_inner_type(T.nilable(String)) # => String
201
+ sig { params(type: T.untyped).returns(T.untyped) }
158
202
  def extract_inner_type(type)
159
203
  return type if type.nil?
160
204
 
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EasyTalk
4
+ module Types
5
+ # Represents a tuple type for arrays with positional type validation.
6
+ #
7
+ # A tuple is an array where each position has a specific type. This class
8
+ # stores the types for each position.
9
+ #
10
+ # @example Basic tuple
11
+ # T::Tuple[String, Integer] # First item must be String, second must be Integer
12
+ #
13
+ # @example With additional_items constraint
14
+ # property :flags, T::Tuple[T::Boolean, T::Boolean], additional_items: false
15
+ #
16
+ class Tuple
17
+ extend T::Sig
18
+
19
+ # @return [Array<Object>] The types for each position in the tuple
20
+ sig { returns(T::Array[T.untyped]) }
21
+ attr_reader :types
22
+
23
+ # Creates a new Tuple instance with the given positional types.
24
+ #
25
+ # @param types [Array] The types for each position in the tuple
26
+ # @raise [ArgumentError] if types is empty or contains nil values
27
+ sig { params(types: T.untyped).void }
28
+ def initialize(*types)
29
+ raise ArgumentError, 'Tuple requires at least one type' if types.empty?
30
+ raise ArgumentError, 'Tuple types cannot be nil' if types.any?(&:nil?)
31
+
32
+ @types = types.freeze
33
+ end
34
+
35
+ # Returns a string representation of the tuple type.
36
+ #
37
+ # @return [String] A human-readable representation
38
+ sig { returns(String) }
39
+ def to_s
40
+ type_names = @types.map { |t| (t.respond_to?(:name) && t.name) || t.to_s }
41
+ "T::Tuple[#{type_names.join(', ')}]"
42
+ end
43
+
44
+ # Returns the name of this type (used by Property for error messages).
45
+ #
46
+ # @return [String] The type name
47
+ sig { returns(String) }
48
+ def name
49
+ to_s
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ # Add T::Tuple module for bracket syntax
56
+ module T
57
+ # Provides tuple type syntax: T::Tuple[Type1, Type2, ...]
58
+ #
59
+ # Creates a tuple type that validates array elements by position.
60
+ #
61
+ # @example Basic usage
62
+ # property :coordinates, T::Tuple[Float, Float]
63
+ # property :record, T::Tuple[String, Integer, T::Boolean]
64
+ #
65
+ # @example With additional_items constraint
66
+ # property :flags, T::Tuple[T::Boolean, T::Boolean], additional_items: false
67
+ #
68
+ module Tuple
69
+ # Creates a new Tuple type with the given positional types.
70
+ #
71
+ # @param types [Array] The types for each position
72
+ # @return [EasyTalk::Types::Tuple] A new Tuple instance
73
+ def self.[](*types)
74
+ EasyTalk::Types::Tuple.new(*types)
75
+ end
76
+ end
77
+ end