@@ -31,6 +31,56 @@ def initialize(model: nil, provider: nil, assume_model_exists: false, context: n
31
31
}
32
32
end
33
33
34
+ ##
35
+ # This method lets you ensure the responses follow a schema you define like this:
36
+ #
37
+ # chat.with_response_format(:integer).ask("What is 2 + 2?").to_i
38
+ # # => 4
39
+ # chat.with_response_format(:string).ask("Say 'Hello World' and nothing else.").content
40
+ # # => "Hello World"
41
+ # chat.with_response_format(:array, items: { type: :string })
42
+ # chat.ask('What are the 2 largest countries? Only respond with country names.').content
43
+ # # => ["Russia", "Canada"]
44
+ # chat.with_response_format(:object, properties: { age: { type: :integer } })
45
+ # chat.ask('Provide sample customer age between 10 and 100.').content
46
+ # # => { "age" => 42 }
47
+ # chat.with_response_format(
48
+ # :object,
49
+ # properties: { hobbies: { type: :array, items: { type: :string, enum: %w[Soccer Golf Hockey] } } }
50
+ # )
51
+ # chat.ask('Provide at least 1 hobby.').content
52
+ # # => { "hobbies" => ["Soccer"] }
53
+ #
54
+ # You can also provide the JSON schema you want directly to the method like this:
55
+ # chat.with_response_format(type: :object, properties: { age: { type: :integer } })
56
+ # # => { "age" => 31 }
57
+ #
58
+ # In this example the code is automatically switching to OpenAI's json_mode since no object
59
+ # properties are requested:
60
+ # chat.with_response_format(:json) # Don't care about structure, just give me JSON
61
+ # chat.ask('Provide a sample customer data object with name and email keys.').content
62
+ # # => { "name" => "Tobias", "email" => "[email protected] " }
63
+ # chat.ask('Provide a sample customer data object with name and email keys.').content
64
+ # # => { "first_name" => "Michael", "email_address" => "[email protected] " }
65
+ #
66
+ # @param type [Symbol] (optional) This can be anything supported by the API JSON schema types (integer, object, etc)
67
+ # @param schema [Hash] The schema for the response format. It can be a JSON schema or a simple hash.
68
+ # @return [Chat] (self)
69
+ def with_response_format ( type = nil , **schema )
70
+ schema_hash = if type . is_a? ( Symbol ) || type . is_a? ( String )
71
+ { type : type == :json ? :object : type }
72
+ elsif type . is_a? ( Hash )
73
+ type
74
+ else
75
+ { }
76
+ end . merge ( schema )
77
+
78
+ @response_schema = Schema . new ( schema_hash )
79
+
80
+ self
81
+ end
82
+ alias with_structured_response with_response_format
83
+
34
84
def ask ( message = nil , with : { } , &block )
35
85
add_message role : :user , content : Content . new ( message , with )
36
86
complete ( &block )
@@ -96,14 +146,16 @@ def each(&)
96
146
97
147
def complete ( &) # rubocop:disable Metrics/MethodLength
98
148
@on [ :new_message ] &.call
99
- response = @provider . complete (
100
- messages ,
101
- tools : @tools ,
102
- temperature : @temperature ,
103
- model : @model . id ,
104
- connection : @connection ,
105
- &
106
- )
149
+ response = @provider . with_response_schema ( @response_schema ) do
150
+ @provider . complete (
151
+ messages ,
152
+ tools : @tools ,
153
+ temperature : @temperature ,
154
+ model : @model . id ,
155
+ connection : @connection ,
156
+ &
157
+ )
158
+ end
107
159
@on [ :end_message ] &.call ( response )
108
160
109
161
add_message response
0 commit comments