prompt_builder 0.1.0
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 +7 -0
- data/CHANGELOG.md +24 -0
- data/MIT-LICENSE +20 -0
- data/README.md +763 -0
- data/VERSION +1 -0
- data/lib/prompt_builder/content/base.rb +44 -0
- data/lib/prompt_builder/content/input_file.rb +63 -0
- data/lib/prompt_builder/content/input_image.rb +64 -0
- data/lib/prompt_builder/content/input_text.rb +42 -0
- data/lib/prompt_builder/content/input_video.rb +43 -0
- data/lib/prompt_builder/content/output_text.rb +59 -0
- data/lib/prompt_builder/content/reasoning_text.rb +42 -0
- data/lib/prompt_builder/content/refusal_content.rb +42 -0
- data/lib/prompt_builder/content/summary_text.rb +42 -0
- data/lib/prompt_builder/content/text.rb +42 -0
- data/lib/prompt_builder/content.rb +28 -0
- data/lib/prompt_builder/errors.rb +18 -0
- data/lib/prompt_builder/items/base.rb +41 -0
- data/lib/prompt_builder/items/compaction.rb +60 -0
- data/lib/prompt_builder/items/function_call.rb +97 -0
- data/lib/prompt_builder/items/function_call_output.rb +110 -0
- data/lib/prompt_builder/items/item_reference.rb +42 -0
- data/lib/prompt_builder/items/message.rb +113 -0
- data/lib/prompt_builder/items/reasoning.rb +75 -0
- data/lib/prompt_builder/items.rb +13 -0
- data/lib/prompt_builder/response.rb +257 -0
- data/lib/prompt_builder/serializers/base.rb +37 -0
- data/lib/prompt_builder/serializers/chat_completion/request.rb +389 -0
- data/lib/prompt_builder/serializers/chat_completion/response.rb +139 -0
- data/lib/prompt_builder/serializers/chat_completion.rb +30 -0
- data/lib/prompt_builder/serializers/converse/request.rb +623 -0
- data/lib/prompt_builder/serializers/converse/response.rb +140 -0
- data/lib/prompt_builder/serializers/converse.rb +30 -0
- data/lib/prompt_builder/serializers/gemini/request.rb +562 -0
- data/lib/prompt_builder/serializers/gemini/response.rb +233 -0
- data/lib/prompt_builder/serializers/gemini.rb +30 -0
- data/lib/prompt_builder/serializers/messages/request.rb +634 -0
- data/lib/prompt_builder/serializers/messages/response.rb +157 -0
- data/lib/prompt_builder/serializers/messages.rb +30 -0
- data/lib/prompt_builder/serializers/open_responses/request.rb +229 -0
- data/lib/prompt_builder/serializers/open_responses/response.rb +18 -0
- data/lib/prompt_builder/serializers/open_responses.rb +30 -0
- data/lib/prompt_builder/serializers.rb +35 -0
- data/lib/prompt_builder/session.rb +383 -0
- data/lib/prompt_builder/tool_registry.rb +75 -0
- data/lib/prompt_builder/tools/definition.rb +66 -0
- data/lib/prompt_builder/tools.rb +7 -0
- data/lib/prompt_builder/usage.rb +100 -0
- data/lib/prompt_builder.rb +86 -0
- data/prompt_builder.gemspec +41 -0
- metadata +107 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PromptBuilder
|
|
4
|
+
# The main DSL entry point for building Open Responses API request payloads.
|
|
5
|
+
# Manages conversation items, tool registration, and serialization to
|
|
6
|
+
# multiple API formats.
|
|
7
|
+
class Session
|
|
8
|
+
# Boolean fields that may legitimately be false; serialized with a nil? check.
|
|
9
|
+
BOOLEAN_FIELDS = %i[parallel_tool_calls stream background store].freeze
|
|
10
|
+
private_constant :BOOLEAN_FIELDS
|
|
11
|
+
|
|
12
|
+
# String-typed fields coerced with .to_s on assignment.
|
|
13
|
+
STRING_FIELDS = %i[
|
|
14
|
+
model instructions previous_response_id truncation safety_identifier prompt_cache_key prompt_cache_retention service_tier
|
|
15
|
+
].freeze
|
|
16
|
+
private_constant :STRING_FIELDS
|
|
17
|
+
|
|
18
|
+
# Float-typed fields coerced with .to_f on assignment.
|
|
19
|
+
FLOAT_FIELDS = %i[temperature top_p presence_penalty frequency_penalty].freeze
|
|
20
|
+
private_constant :FLOAT_FIELDS
|
|
21
|
+
|
|
22
|
+
# Integer-typed fields coerced with .to_i on assignment.
|
|
23
|
+
INTEGER_FIELDS = %i[max_output_tokens max_tool_calls top_logprobs].freeze
|
|
24
|
+
private_constant :INTEGER_FIELDS
|
|
25
|
+
|
|
26
|
+
# Complex fields serialized to JSON-compatible values via PromptBuilder.jsonify.
|
|
27
|
+
JSONIFY_FIELDS = %i[include tool_choice metadata text stream_options reasoning].freeze
|
|
28
|
+
private_constant :JSONIFY_FIELDS
|
|
29
|
+
|
|
30
|
+
# @!attribute [rw] model
|
|
31
|
+
# @return [String, nil] the model identifier
|
|
32
|
+
# @!attribute [rw] instructions
|
|
33
|
+
# @return [String, nil] the system instructions
|
|
34
|
+
# @!attribute [rw] previous_response_id
|
|
35
|
+
# @return [String, nil] the previous response identifier for server-side state
|
|
36
|
+
# @!attribute [rw] truncation
|
|
37
|
+
# @return [String, nil] the truncation strategy
|
|
38
|
+
# @!attribute [rw] safety_identifier
|
|
39
|
+
# @return [String, nil] the safety identifier
|
|
40
|
+
# @!attribute [rw] prompt_cache_key
|
|
41
|
+
# @return [String, nil] the prompt cache key
|
|
42
|
+
# @!attribute [rw] prompt_cache_retention
|
|
43
|
+
# @return [String, nil] the prompt cache retention policy
|
|
44
|
+
# @!attribute [rw] service_tier
|
|
45
|
+
# @return [String, nil] the service tier
|
|
46
|
+
STRING_FIELDS.each do |f|
|
|
47
|
+
attr_reader f
|
|
48
|
+
define_method(:"#{f}=") { |v| instance_variable_set(:"@#{f}", v.nil? ? nil : v.to_s) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @!attribute [rw] temperature
|
|
52
|
+
# @return [Float, nil] the temperature
|
|
53
|
+
# @!attribute [rw] top_p
|
|
54
|
+
# @return [Float, nil] the top_p sampling parameter
|
|
55
|
+
# @!attribute [rw] presence_penalty
|
|
56
|
+
# @return [Float, nil] the presence penalty
|
|
57
|
+
# @!attribute [rw] frequency_penalty
|
|
58
|
+
# @return [Float, nil] the frequency penalty
|
|
59
|
+
FLOAT_FIELDS.each do |f|
|
|
60
|
+
attr_reader f
|
|
61
|
+
define_method(:"#{f}=") { |v| instance_variable_set(:"@#{f}", v.nil? ? nil : v.to_f) }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @!attribute [rw] max_output_tokens
|
|
65
|
+
# @return [Integer, nil] the maximum output tokens
|
|
66
|
+
# @!attribute [rw] max_tool_calls
|
|
67
|
+
# @return [Integer, nil] the maximum number of tool calls
|
|
68
|
+
# @!attribute [rw] top_logprobs
|
|
69
|
+
# @return [Integer, nil] the number of top log probabilities to return
|
|
70
|
+
INTEGER_FIELDS.each do |f|
|
|
71
|
+
attr_reader f
|
|
72
|
+
define_method(:"#{f}=") { |v| instance_variable_set(:"@#{f}", v.nil? ? nil : v.to_i) }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @!attribute [rw] parallel_tool_calls
|
|
76
|
+
# @return [Boolean, nil] whether parallel tool calls are enabled
|
|
77
|
+
# @!attribute [rw] stream
|
|
78
|
+
# @return [Boolean, nil] whether to stream the response
|
|
79
|
+
# @!attribute [rw] background
|
|
80
|
+
# @return [Boolean, nil] whether this is a background request
|
|
81
|
+
# @!attribute [rw] store
|
|
82
|
+
# @return [Boolean, nil] whether to store the response
|
|
83
|
+
BOOLEAN_FIELDS.each do |f|
|
|
84
|
+
attr_reader f
|
|
85
|
+
define_method(:"#{f}=") { |v| instance_variable_set(:"@#{f}", v) }
|
|
86
|
+
alias_method :"#{f}?", f
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @!attribute [rw] include
|
|
90
|
+
# @return [Array, nil] fields to include in the response
|
|
91
|
+
# @!attribute [rw] tool_choice
|
|
92
|
+
# @return [String, Hash, nil] the tool choice configuration
|
|
93
|
+
# @!attribute [rw] metadata
|
|
94
|
+
# @return [Hash, nil] arbitrary metadata
|
|
95
|
+
# @!attribute [rw] text
|
|
96
|
+
# @return [Hash, nil] text output configuration
|
|
97
|
+
# @!attribute [rw] stream_options
|
|
98
|
+
# @return [Hash, nil] stream configuration options
|
|
99
|
+
# @!attribute [rw] reasoning
|
|
100
|
+
# @return [Hash, nil] the reasoning configuration
|
|
101
|
+
JSONIFY_FIELDS.each do |f|
|
|
102
|
+
attr_reader f
|
|
103
|
+
define_method(:"#{f}=") { |v| instance_variable_set(:"@#{f}", v.nil? ? nil : PromptBuilder.jsonify(v)) }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @return [Array<Items::Base>] all conversation items
|
|
107
|
+
attr_reader :items
|
|
108
|
+
|
|
109
|
+
# @return [Integer] the index in +items+ marking the boundary after the last response
|
|
110
|
+
attr_reader :response_boundary_index
|
|
111
|
+
|
|
112
|
+
# @return [Hash, nil] provider-specific extra data for serializers.
|
|
113
|
+
# Recognized keys vary by target format. Unrecognized keys are silently
|
|
114
|
+
# ignored by each serializer.
|
|
115
|
+
attr_reader :extra
|
|
116
|
+
|
|
117
|
+
class << self
|
|
118
|
+
# Deserialize a Session from a Hash produced by +to_h+ or parsed JSON.
|
|
119
|
+
# Reconstructs all config fields and conversation items. Tool definitions
|
|
120
|
+
# are restored without handlers; re-register handlers separately if you
|
|
121
|
+
# need to invoke the tools.
|
|
122
|
+
#
|
|
123
|
+
# @param hash [Hash] a Hash with string keys
|
|
124
|
+
# @return [Session]
|
|
125
|
+
def from_h(hash)
|
|
126
|
+
attrs = (STRING_FIELDS + FLOAT_FIELDS + INTEGER_FIELDS + BOOLEAN_FIELDS + JSONIFY_FIELDS)
|
|
127
|
+
.each_with_object({}) { |f, acc| acc[f] = hash[f.to_s] }
|
|
128
|
+
attrs[:extra] = hash["extra"] if hash["extra"]
|
|
129
|
+
session = new(**attrs)
|
|
130
|
+
|
|
131
|
+
Array(hash["input"]).each do |item_hash|
|
|
132
|
+
session.add_item(Items::Base.from_h(item_hash))
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
Array(hash["tools"]).each do |tool_hash|
|
|
136
|
+
defn = Tools::Definition.from_h(tool_hash)
|
|
137
|
+
extra = defn.extra.transform_keys(&:to_sym)
|
|
138
|
+
session.register_tool(defn.name, description: defn.description, parameters: defn.parameters, strict: defn.strict, **extra)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
session
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Create a new Session with the given options.
|
|
146
|
+
# Accepts keyword arguments for all typed field groups (STRING_FIELDS,
|
|
147
|
+
# FLOAT_FIELDS, INTEGER_FIELDS, BOOLEAN_FIELDS, JSONIFY_FIELDS); all default
|
|
148
|
+
# to +nil+. The +input+ shorthand auto-creates a user message if provided.
|
|
149
|
+
#
|
|
150
|
+
# @param attributes [Hash] keyword options; see attribute declarations above
|
|
151
|
+
# @option attributes [String, nil] :input optional string shorthand; a user
|
|
152
|
+
# message is automatically added with this text
|
|
153
|
+
# @option attributes [Hash, nil] :extra provider-specific extra data for
|
|
154
|
+
# serializers; recognized keys vary by target format
|
|
155
|
+
def initialize(**attributes)
|
|
156
|
+
(STRING_FIELDS + FLOAT_FIELDS + INTEGER_FIELDS + BOOLEAN_FIELDS + JSONIFY_FIELDS).each do |f|
|
|
157
|
+
send(:"#{f}=", attributes[f])
|
|
158
|
+
end
|
|
159
|
+
@extra = PromptBuilder.jsonify(attributes[:extra]) if attributes[:extra]
|
|
160
|
+
@items = []
|
|
161
|
+
@tool_definitions = {}
|
|
162
|
+
@response_boundary_index = 0
|
|
163
|
+
user(attributes[:input]) if attributes[:input]
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Add a user message to the conversation.
|
|
167
|
+
#
|
|
168
|
+
# @param content [String, Content::Base, Hash, Array<Content::Base>, Array<Hash>] the message content
|
|
169
|
+
# @return [Items::Message] the added message
|
|
170
|
+
# @example
|
|
171
|
+
# session.user("Hello, how are you?")
|
|
172
|
+
# session.user(Content::InputText.new(text: "Hello, how are you?"))
|
|
173
|
+
# session.user(type: "input_text", text: "Hello, how are you?")
|
|
174
|
+
# session.user([
|
|
175
|
+
# Content::InputText.new(text: "What is in this image?"),
|
|
176
|
+
# Content::InputImage.new(url: "http://example.com/image.png")
|
|
177
|
+
# ])
|
|
178
|
+
# session.user([
|
|
179
|
+
# {type: "input_text", text: "What is in this image?"},
|
|
180
|
+
# {type: "input_image", url: "http://example.com/image.png"}
|
|
181
|
+
# ])
|
|
182
|
+
def user(content)
|
|
183
|
+
add_item(Items::Message.new(role: "user", content: content))
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Add an assistant message to the conversation.
|
|
187
|
+
#
|
|
188
|
+
# @param content [String, Content::Base, Hash, Array<Content::Base>, Array<Hash>] the message content
|
|
189
|
+
# @return [Items::Message] the added message
|
|
190
|
+
def assistant(content)
|
|
191
|
+
add_item(Items::Message.new(role: "assistant", content: content))
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Add a system message to the conversation.
|
|
195
|
+
#
|
|
196
|
+
# @param content [String, Content::Base, Hash, Array<Content::Base>, Array<Hash>] the message content
|
|
197
|
+
# @return [Items::Message] the added message
|
|
198
|
+
def system(content)
|
|
199
|
+
add_item(Items::Message.new(role: "system", content: content))
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Add a developer message to the conversation.
|
|
203
|
+
#
|
|
204
|
+
# @param content [String, Content::Base, Hash, Array<Content::Base>, Array<Hash>] the message content
|
|
205
|
+
# @return [Items::Message] the added message
|
|
206
|
+
def developer(content)
|
|
207
|
+
add_item(Items::Message.new(role: "developer", content: content))
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Add a tool call output to the conversation.
|
|
211
|
+
#
|
|
212
|
+
# @param call_id [String] the tool call identifier
|
|
213
|
+
# @param result [String, Hash, Array, Content::Base, nil] the tool call result
|
|
214
|
+
# @return [Items::FunctionOutput] the added function output item
|
|
215
|
+
def add_function_call_output(call_id:, result:)
|
|
216
|
+
add_item(Items::FunctionOutput.new(call_id: call_id, result: result))
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Add a raw item to the conversation.
|
|
220
|
+
#
|
|
221
|
+
# @param item [Items::Base] the item to add
|
|
222
|
+
# @return [Items::Base] the added item
|
|
223
|
+
def add_item(item)
|
|
224
|
+
raise ArgumentError, "item must be an instance of Items::Base" unless item.is_a?(Items::Base)
|
|
225
|
+
|
|
226
|
+
@items << item
|
|
227
|
+
item
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Add a response to the conversation. Output items are always appended
|
|
231
|
+
# to +items+ so that the full history is available locally. When the
|
|
232
|
+
# session is in server state mode (previous_response_id already set),
|
|
233
|
+
# the id is also updated so +to_h+ can use it as a serialization
|
|
234
|
+
# optimization.
|
|
235
|
+
#
|
|
236
|
+
# @param response [Response] the API response
|
|
237
|
+
# @return [void]
|
|
238
|
+
def add_response(response)
|
|
239
|
+
raise ArgumentError, "response must be an instance of Response" unless response.is_a?(Response)
|
|
240
|
+
|
|
241
|
+
@items.concat(response.output)
|
|
242
|
+
# Only refresh previous_response_id when the session is already in
|
|
243
|
+
# server-state mode AND the response actually carries an id; otherwise
|
|
244
|
+
# leave the existing pointer alone (responses from formats that don't
|
|
245
|
+
# populate `id` would otherwise silently drop us back into local state).
|
|
246
|
+
self.previous_response_id = response.id if !local_state? && response.id
|
|
247
|
+
@response_boundary_index = @items.length
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Register a tool on this session.
|
|
251
|
+
#
|
|
252
|
+
# @param name [String] the tool name
|
|
253
|
+
# @param description [String, nil] the tool description
|
|
254
|
+
# @param parameters [Hash, nil] the JSON Schema for parameters
|
|
255
|
+
# @param strict [Boolean] whether strict mode is enabled
|
|
256
|
+
# @param extra [Hash] provider-specific extra keyword arguments (e.g. cache_control)
|
|
257
|
+
# @return [Tools::Definition] the registered definition
|
|
258
|
+
def register_tool(name, description: nil, parameters: nil, strict: false, **extra)
|
|
259
|
+
definition = Tools::Definition.new(
|
|
260
|
+
name: name,
|
|
261
|
+
description: description,
|
|
262
|
+
parameters: parameters,
|
|
263
|
+
strict: strict,
|
|
264
|
+
**extra
|
|
265
|
+
)
|
|
266
|
+
@tool_definitions[name] = definition
|
|
267
|
+
definition
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Register all tools from a ToolRegistry.
|
|
271
|
+
#
|
|
272
|
+
# @param registry [ToolRegistry] the registry to copy tools from
|
|
273
|
+
# @return [void]
|
|
274
|
+
def register_tools(registry)
|
|
275
|
+
raise ArgumentError, "registry must be an instance of ToolRegistry" unless registry.is_a?(ToolRegistry)
|
|
276
|
+
|
|
277
|
+
registry.definitions.each do |defn|
|
|
278
|
+
extra = defn.extra.transform_keys(&:to_sym)
|
|
279
|
+
register_tool(
|
|
280
|
+
defn.name,
|
|
281
|
+
description: defn.description,
|
|
282
|
+
parameters: defn.parameters,
|
|
283
|
+
strict: defn.strict,
|
|
284
|
+
**extra
|
|
285
|
+
)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Return all tool definitions registered on this session.
|
|
290
|
+
#
|
|
291
|
+
# @return [Array<Tools::Definition>] all tool definitions
|
|
292
|
+
def tool_definitions
|
|
293
|
+
@tool_definitions.values
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Create a new Session with the same configuration and tools but no items.
|
|
297
|
+
#
|
|
298
|
+
# @return [Session] a new session with cloned configuration
|
|
299
|
+
def clone_config
|
|
300
|
+
session = Session.new(**config_hash)
|
|
301
|
+
@tool_definitions.each do |name, defn|
|
|
302
|
+
extra = defn.extra.transform_keys(&:to_sym)
|
|
303
|
+
session.register_tool(
|
|
304
|
+
name,
|
|
305
|
+
description: defn.description,
|
|
306
|
+
parameters: defn.parameters,
|
|
307
|
+
strict: defn.strict,
|
|
308
|
+
**extra
|
|
309
|
+
)
|
|
310
|
+
end
|
|
311
|
+
session
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Check if this session is in local state mode (no previous_response_id). This
|
|
315
|
+
# indicates that the full conversation history is stored in the session and will be
|
|
316
|
+
# sent with each request. Once a response with an id is added, the session switches
|
|
317
|
+
# to server state mode, where only new items after the last response are sent
|
|
318
|
+
# and the previous_response_id is used to reference the last response.
|
|
319
|
+
#
|
|
320
|
+
# @return [Boolean]
|
|
321
|
+
def local_state?
|
|
322
|
+
@previous_response_id.nil?
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# Serialize to an Open Responses API request Hash with string keys.
|
|
326
|
+
#
|
|
327
|
+
# @return [Hash]
|
|
328
|
+
def to_h
|
|
329
|
+
h = {}
|
|
330
|
+
h["model"] = @model if @model
|
|
331
|
+
h["instructions"] = @instructions if @instructions
|
|
332
|
+
|
|
333
|
+
h["input"] = @items.map(&:to_h) unless @items.empty?
|
|
334
|
+
h["previous_response_id"] = @previous_response_id if @previous_response_id
|
|
335
|
+
|
|
336
|
+
h["tools"] = tool_definitions.map(&:to_h) unless @tool_definitions.empty?
|
|
337
|
+
|
|
338
|
+
(STRING_FIELDS - %i[model instructions previous_response_id]).each do |f|
|
|
339
|
+
val = send(f)
|
|
340
|
+
h[f.to_s] = val if val
|
|
341
|
+
end
|
|
342
|
+
FLOAT_FIELDS.each { |f|
|
|
343
|
+
val = send(f)
|
|
344
|
+
h[f.to_s] = val if val
|
|
345
|
+
}
|
|
346
|
+
INTEGER_FIELDS.each { |f|
|
|
347
|
+
val = send(f)
|
|
348
|
+
h[f.to_s] = val if val
|
|
349
|
+
}
|
|
350
|
+
BOOLEAN_FIELDS.each { |f|
|
|
351
|
+
val = send(f)
|
|
352
|
+
h[f.to_s] = val unless val.nil?
|
|
353
|
+
}
|
|
354
|
+
JSONIFY_FIELDS.each { |f|
|
|
355
|
+
val = send(f)
|
|
356
|
+
h[f.to_s] = val if val
|
|
357
|
+
}
|
|
358
|
+
h["extra"] = @extra if @extra
|
|
359
|
+
|
|
360
|
+
h
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Export this session to an alternate API format using the given serializer.
|
|
364
|
+
#
|
|
365
|
+
# @param serializer_class [Class, Symbol] a serializer class (e.g. Serializers::ChatCompletion)
|
|
366
|
+
# or a symbol shorthand (+:open_responses+, +:chat_completion+, +:messages+,
|
|
367
|
+
# +:gemini+, +:converse+)
|
|
368
|
+
# @return [Hash] the serialized request payload
|
|
369
|
+
# @raise [ArgumentError] if a symbol is given that does not map to a known serializer
|
|
370
|
+
def request_payload(serializer_class)
|
|
371
|
+
Serializers.resolve(serializer_class).request_payload(self)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
private
|
|
375
|
+
|
|
376
|
+
def config_hash
|
|
377
|
+
h = (STRING_FIELDS + FLOAT_FIELDS + INTEGER_FIELDS + BOOLEAN_FIELDS + JSONIFY_FIELDS - %i[previous_response_id])
|
|
378
|
+
.each_with_object({}) { |f, acc| acc[f] = send(f) }
|
|
379
|
+
h[:extra] = @extra if @extra
|
|
380
|
+
h
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PromptBuilder
|
|
4
|
+
# Registry for tool definitions and their handler callables.
|
|
5
|
+
class ToolRegistry
|
|
6
|
+
# Create a new ToolRegistry.
|
|
7
|
+
def initialize
|
|
8
|
+
@definitions = {}
|
|
9
|
+
@handlers = {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Register a tool with its definition and handler.
|
|
13
|
+
#
|
|
14
|
+
# @param name [String, Symbol] the tool name
|
|
15
|
+
# @param description [String, nil] the tool description
|
|
16
|
+
# @param parameters [Hash, nil] the JSON Schema for parameters
|
|
17
|
+
# @param strict [Boolean] whether strict mode is enabled
|
|
18
|
+
# @param callable [#call, nil] a callable handler (alternative to block)
|
|
19
|
+
# @param extra [Hash] provider-specific extra keyword arguments (e.g. cache_control)
|
|
20
|
+
# @yield [Hash] the parsed arguments when the tool is invoked
|
|
21
|
+
# @yieldreturn [Object] the tool output (String, Hash, Array, or any object)
|
|
22
|
+
# @return [Tools::Definition] the registered definition
|
|
23
|
+
def register(name, description: nil, parameters: nil, strict: false, callable: nil, **extra, &handler)
|
|
24
|
+
name = name.to_s
|
|
25
|
+
raise ArgumentError.new("Tool name is required") if name.empty?
|
|
26
|
+
|
|
27
|
+
definition = Tools::Definition.new(
|
|
28
|
+
name: name,
|
|
29
|
+
description: description,
|
|
30
|
+
parameters: parameters,
|
|
31
|
+
strict: strict,
|
|
32
|
+
**extra
|
|
33
|
+
)
|
|
34
|
+
@definitions[name] = definition
|
|
35
|
+
@handlers[name] = callable || handler
|
|
36
|
+
definition
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Look up a handler by name.
|
|
40
|
+
#
|
|
41
|
+
# @param name [String] the tool name
|
|
42
|
+
# @return [#call, nil] the handler, or nil if not found
|
|
43
|
+
def handler_for(name)
|
|
44
|
+
@handlers[name]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Look up a definition by name.
|
|
48
|
+
#
|
|
49
|
+
# @param name [String] the tool name
|
|
50
|
+
# @return [Tools::Definition, nil] the definition, or nil if not found
|
|
51
|
+
def definition_for(name)
|
|
52
|
+
@definitions[name]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Return all tool definitions.
|
|
56
|
+
#
|
|
57
|
+
# @return [Array<Tools::Definition>] all tool definitions
|
|
58
|
+
def definitions
|
|
59
|
+
@definitions.values
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Invoke a tool by name with the given arguments.
|
|
63
|
+
#
|
|
64
|
+
# @param name [String] the tool name
|
|
65
|
+
# @param arguments [Hash] the parsed arguments
|
|
66
|
+
# @return [Object] the raw tool handler return value
|
|
67
|
+
# @raise [ToolNotFoundError] if no handler is found for the given name
|
|
68
|
+
def invoke(name, arguments)
|
|
69
|
+
handler = handler_for(name)
|
|
70
|
+
raise ToolNotFoundError, "No handler registered for tool: #{name.inspect}" unless handler
|
|
71
|
+
|
|
72
|
+
handler.call(arguments)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PromptBuilder
|
|
4
|
+
module Tools
|
|
5
|
+
# Represents a tool definition that describes a function available to the model.
|
|
6
|
+
class Definition
|
|
7
|
+
# @return [String] the tool name
|
|
8
|
+
attr_reader :name
|
|
9
|
+
|
|
10
|
+
# @return [String, nil] the tool description
|
|
11
|
+
attr_reader :description
|
|
12
|
+
|
|
13
|
+
# @return [Hash, nil] the JSON Schema for the tool parameters
|
|
14
|
+
attr_reader :parameters
|
|
15
|
+
|
|
16
|
+
# @return [Boolean] whether strict mode is enabled
|
|
17
|
+
attr_reader :strict
|
|
18
|
+
|
|
19
|
+
# @return [Hash, nil] provider-specific extra data (e.g. cache_control)
|
|
20
|
+
attr_reader :extra
|
|
21
|
+
|
|
22
|
+
# Create a new tool Definition.
|
|
23
|
+
#
|
|
24
|
+
# @param name [String] the tool name
|
|
25
|
+
# @param description [String, nil] the tool description
|
|
26
|
+
# @param parameters [Hash, nil] the JSON Schema for the parameters
|
|
27
|
+
# @param strict [Boolean] whether strict mode is enabled
|
|
28
|
+
# @param extra [Hash] provider-specific extra keyword arguments
|
|
29
|
+
def initialize(name:, description: nil, parameters: nil, strict: false, **extra)
|
|
30
|
+
@name = name&.to_s
|
|
31
|
+
@description = description&.to_s
|
|
32
|
+
@parameters = PromptBuilder.jsonify(parameters)
|
|
33
|
+
@strict = strict ? true : false
|
|
34
|
+
@extra = extra.transform_keys(&:to_s)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class << self
|
|
38
|
+
# Deserialize a Definition from a Hash.
|
|
39
|
+
#
|
|
40
|
+
# @param hash [Hash] a Hash with string keys
|
|
41
|
+
# @return [Definition]
|
|
42
|
+
def from_h(hash)
|
|
43
|
+
new(
|
|
44
|
+
name: hash["name"],
|
|
45
|
+
description: hash["description"],
|
|
46
|
+
parameters: hash["parameters"],
|
|
47
|
+
strict: hash.fetch("strict", false),
|
|
48
|
+
**hash.except("type", "name", "description", "parameters", "strict").transform_keys(&:to_sym)
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Serialize to a Hash with string keys. Nil values are omitted.
|
|
54
|
+
#
|
|
55
|
+
# @return [Hash]
|
|
56
|
+
def to_h
|
|
57
|
+
h = {"type" => "function", "name" => @name}
|
|
58
|
+
h["description"] = @description if @description
|
|
59
|
+
h["parameters"] = @parameters if @parameters
|
|
60
|
+
h["strict"] = @strict if @strict
|
|
61
|
+
h = PromptBuilder.jsonify(@extra).merge(h) unless @extra.empty?
|
|
62
|
+
h
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PromptBuilder
|
|
4
|
+
# Value object representing token usage statistics from an API response.
|
|
5
|
+
class Usage
|
|
6
|
+
# @return [Integer, nil] number of input tokens
|
|
7
|
+
attr_reader :input_tokens
|
|
8
|
+
|
|
9
|
+
# @return [Integer, nil] number of output tokens
|
|
10
|
+
attr_reader :output_tokens
|
|
11
|
+
|
|
12
|
+
# @return [Integer, nil] total number of tokens
|
|
13
|
+
attr_reader :total_tokens
|
|
14
|
+
|
|
15
|
+
# @return [Hash, nil] input token details
|
|
16
|
+
attr_reader :input_tokens_details
|
|
17
|
+
|
|
18
|
+
# @return [Hash, nil] output token details
|
|
19
|
+
attr_reader :output_tokens_details
|
|
20
|
+
|
|
21
|
+
# Create a new Usage value object.
|
|
22
|
+
#
|
|
23
|
+
# @param input_tokens [Integer, nil] number of input tokens
|
|
24
|
+
# @param output_tokens [Integer, nil] number of output tokens
|
|
25
|
+
# @param total_tokens [Integer, nil] total number of tokens
|
|
26
|
+
# @param input_tokens_details [Hash, nil] input token details
|
|
27
|
+
# @param output_tokens_details [Hash, nil] output token details
|
|
28
|
+
# @param reasoning_tokens [Integer, nil] number of reasoning tokens
|
|
29
|
+
def initialize(input_tokens: nil, output_tokens: nil, total_tokens: nil,
|
|
30
|
+
input_tokens_details: nil, output_tokens_details: nil, reasoning_tokens: nil)
|
|
31
|
+
@input_tokens = input_tokens&.to_i
|
|
32
|
+
@output_tokens = output_tokens&.to_i
|
|
33
|
+
@total_tokens = total_tokens&.to_i
|
|
34
|
+
@input_tokens_details = PromptBuilder.jsonify(input_tokens_details)
|
|
35
|
+
@output_tokens_details = PromptBuilder.jsonify(output_tokens_details) || build_output_tokens_details(reasoning_tokens)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
# Deserialize a Usage from a Hash.
|
|
40
|
+
#
|
|
41
|
+
# @param hash [Hash] a Hash with string keys
|
|
42
|
+
# @return [Usage]
|
|
43
|
+
def from_h(hash)
|
|
44
|
+
input_tokens_details = hash["input_tokens_details"]
|
|
45
|
+
output_tokens_details = hash["output_tokens_details"]
|
|
46
|
+
|
|
47
|
+
new(
|
|
48
|
+
input_tokens: hash["input_tokens"],
|
|
49
|
+
output_tokens: hash["output_tokens"],
|
|
50
|
+
total_tokens: hash["total_tokens"],
|
|
51
|
+
input_tokens_details: input_tokens_details,
|
|
52
|
+
output_tokens_details: output_tokens_details,
|
|
53
|
+
reasoning_tokens: hash["reasoning_tokens"] || output_tokens_details&.fetch("reasoning_tokens", nil)
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Number of cached input tokens.
|
|
59
|
+
#
|
|
60
|
+
# @return [Integer, nil]
|
|
61
|
+
def cached_tokens
|
|
62
|
+
@input_tokens_details&.fetch("cached_tokens", nil)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Number of tokens used for cache creation. This is specific only to the Anthropic Messages API format.
|
|
66
|
+
#
|
|
67
|
+
# @return [Integer, nil]
|
|
68
|
+
def cache_creation_input_tokens
|
|
69
|
+
@input_tokens_details&.fetch("cache_creation_input_tokens", nil)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Number of reasoning tokens.
|
|
73
|
+
#
|
|
74
|
+
# @return [Integer, nil]
|
|
75
|
+
def reasoning_tokens
|
|
76
|
+
@output_tokens_details&.fetch("reasoning_tokens", nil)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Serialize to a Hash with string keys. Nil values are omitted.
|
|
80
|
+
#
|
|
81
|
+
# @return [Hash]
|
|
82
|
+
def to_h
|
|
83
|
+
h = {}
|
|
84
|
+
h["input_tokens"] = @input_tokens if @input_tokens
|
|
85
|
+
h["output_tokens"] = @output_tokens if @output_tokens
|
|
86
|
+
h["total_tokens"] = @total_tokens if @total_tokens
|
|
87
|
+
h["input_tokens_details"] = @input_tokens_details if @input_tokens_details
|
|
88
|
+
h["output_tokens_details"] = @output_tokens_details if @output_tokens_details
|
|
89
|
+
h
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def build_output_tokens_details(reasoning_tokens)
|
|
95
|
+
return nil unless reasoning_tokens
|
|
96
|
+
|
|
97
|
+
{"reasoning_tokens" => reasoning_tokens}
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|