@@ -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 : nil , &)
35
85
add_message role : :user , content : Content . new ( message , with )
36
86
complete ( &)
@@ -94,17 +144,23 @@ def each(&)
94
144
95
145
def complete ( &)
96
146
@on [ :new_message ] &.call
97
- response = @provider . complete (
98
- messages ,
99
- tools : @tools ,
100
- temperature : @temperature ,
101
- model : @model . id ,
102
- connection : @connection ,
103
- &
104
- )
147
+ response = @provider . with_response_schema ( @response_schema ) do
148
+ @provider . complete (
149
+ messages ,
150
+ tools : @tools ,
151
+ temperature : @temperature ,
152
+ model : @model . id ,
153
+ connection : @connection ,
154
+ &
155
+ )
156
+ end
157
+
105
158
@on [ :end_message ] &.call ( response )
106
159
107
160
add_message response
161
+
162
+ @response_schema = nil # Reset the response schema after completion of this chat thread
163
+
108
164
if response . tool_call?
109
165
handle_tool_calls ( response , &)
110
166
else
0 commit comments