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
data/README.md
ADDED
|
@@ -0,0 +1,763 @@
|
|
|
1
|
+
# PromptBuilder
|
|
2
|
+
|
|
3
|
+
[](https://github.com/bdurand/prompt_builder/actions/workflows/continuous_integration.yml)
|
|
4
|
+
[](https://github.com/testdouble/standard)
|
|
5
|
+
[](https://badge.fury.io/rb/prompt_builder)
|
|
6
|
+
|
|
7
|
+
This gem provides a Ruby DSL for building and parsing LLM API request payloads. The goal of this gem is to provide a single, consistent interface for constructing requests and parsing responses across multiple LLM APIs without locking you into a specific provider or HTTP client. Chat sessions are designed to be serializable so they can be persisted into databases or caches.
|
|
8
|
+
|
|
9
|
+
The [Open Responses API](https://www.openresponses.org/) is used as the internal data model. The [Open Responses reference](https://www.openresponses.org/reference) documentation provides details on how to use the API and the terminology.
|
|
10
|
+
|
|
11
|
+
Requests can be generated for and responses can be parsed from these common LLM API formats:
|
|
12
|
+
|
|
13
|
+
- [Open Responses API](https://www.openresponses.org/)
|
|
14
|
+
- [OpenAI Chat Completions API](https://platform.openai.com/docs/api-reference/chat)
|
|
15
|
+
- [Anthropic Messages API](https://docs.anthropic.com/en/api/messages)
|
|
16
|
+
- [Google Gemini API](https://ai.google.dev/api/generate-content)
|
|
17
|
+
- [Amazon Bedrock Converse API](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html)
|
|
18
|
+
|
|
19
|
+
This gem does **not** include any HTTP client code. It is designed to be used with whatever HTTP library you prefer. You build a request payload, send it to the API yourself, and then parse the response back into Ruby objects.
|
|
20
|
+
|
|
21
|
+
It was specifically designed to work with the [patient_http](https://github.com/bdurand/patient_http) gem to allow making asynchronous requests to LLM APIs and is used in the [patient_llm](https://github.com/bdurand/patient_llm) gem.
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
- [Sessions](#sessions)
|
|
26
|
+
- [Conversation History](#conversation-history)
|
|
27
|
+
- [Serializing Requests](#serializing-requests)
|
|
28
|
+
- [Parsing Responses](#parsing-responses)
|
|
29
|
+
- [Agentic Tool Loops](#agentic-tool-loops)
|
|
30
|
+
- [Tool Registry](#tool-registry)
|
|
31
|
+
- [Content Types](#content-types)
|
|
32
|
+
- [Configuration Options](#configuration-options)
|
|
33
|
+
- [Serialization and Persistence](#serialization-and-persistence)
|
|
34
|
+
- [Serializer Compatibility](#serializer-compatibility)
|
|
35
|
+
|
|
36
|
+
### Sessions
|
|
37
|
+
|
|
38
|
+
The `PromptBuilder::Session` class is the main entry point. A session holds the model configuration, conversation history, and tool definitions needed to build a request payload.
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
session = PromptBuilder::Session.new(
|
|
42
|
+
model: "gpt-5.4",
|
|
43
|
+
instructions: "You are a helpful assistant.",
|
|
44
|
+
temperature: 0.7
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Add a user message to the conversation history
|
|
48
|
+
session.user("What is the capital of France?")
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
You can also pass an `input` shorthand to create a user message in one step:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
session = PromptBuilder::Session.new(
|
|
55
|
+
model: "gpt-5.4",
|
|
56
|
+
input: "What is the capital of France?"
|
|
57
|
+
)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Conversation History
|
|
61
|
+
|
|
62
|
+
Build up a multi-turn conversation by adding messages:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
session = PromptBuilder::Session.new(model: "gpt-5.4")
|
|
66
|
+
session.system("You are a helpful assistant.")
|
|
67
|
+
session.user("Hello!")
|
|
68
|
+
session.assistant("Hi there! How can I help you today?")
|
|
69
|
+
session.user("What's the weather like?")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Messages support the roles `user`, `assistant`, `system`, and `developer`.
|
|
73
|
+
|
|
74
|
+
### Serializing Requests
|
|
75
|
+
|
|
76
|
+
Once you've built a session, serialize it to a request payload for the API you want to call. Five target formats are supported:
|
|
77
|
+
|
|
78
|
+
**Open Responses API**:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
payload = session.to_h
|
|
82
|
+
# or
|
|
83
|
+
payload = session.request_payload(:open_responses)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**OpenAI Chat Completions API**:
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
payload = session.request_payload(:chat_completion)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Anthropic Messages API**:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
payload = session.request_payload(:messages)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Google Gemini API**:
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
payload = session.request_payload(:gemini)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Amazon Bedrock Converse API**:
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
payload = session.request_payload(:converse)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The payload is a plain Ruby `Hash` that you can convert to JSON and send to the API with any HTTP client:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
require "net/http"
|
|
114
|
+
require "json"
|
|
115
|
+
|
|
116
|
+
uri = URI("https://api.openai.com/v1/responses")
|
|
117
|
+
request = Net::HTTP::Post.new(uri)
|
|
118
|
+
request["Authorization"] = "Bearer #{api_key}"
|
|
119
|
+
request["Content-Type"] = "application/json"
|
|
120
|
+
request.body = JSON.generate(session.to_h)
|
|
121
|
+
|
|
122
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
|
123
|
+
http.request(request)
|
|
124
|
+
end
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Parsing Responses
|
|
128
|
+
|
|
129
|
+
Parse an API response back into an `PromptBuilder::Response` object using `Response.parse` with a serializer symbol:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
# Open Responses API
|
|
133
|
+
response = PromptBuilder::Response.parse(JSON.parse(response_body), :open_responses)
|
|
134
|
+
|
|
135
|
+
# OpenAI Chat Completions API
|
|
136
|
+
response = PromptBuilder::Response.parse(JSON.parse(response_body), :chat_completion)
|
|
137
|
+
|
|
138
|
+
# Anthropic Messages API
|
|
139
|
+
response = PromptBuilder::Response.parse(JSON.parse(response_body), :messages)
|
|
140
|
+
|
|
141
|
+
# Google Gemini API
|
|
142
|
+
response = PromptBuilder::Response.parse(JSON.parse(response_body), :gemini)
|
|
143
|
+
|
|
144
|
+
# Amazon Bedrock Converse API
|
|
145
|
+
response = PromptBuilder::Response.parse(JSON.parse(response_body), :converse)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
You can also pass a serializer class directly:
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
response = PromptBuilder::Response.parse(JSON.parse(response_body), PromptBuilder::Serializers::ChatCompletion)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The `Response` object provides convenient accessors:
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
response.text # => "The capital of France is Paris."
|
|
158
|
+
response.completed? # => true
|
|
159
|
+
response.has_tool_calls? # => false
|
|
160
|
+
response.usage # => #<PromptBuilder::Usage input_tokens=25 output_tokens=12 ...>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Agentic Tool Loops
|
|
164
|
+
|
|
165
|
+
You can register tool definitions on a session, add API responses to the conversation, and manually append tool outputs to build an agentic loop:
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
session = PromptBuilder::Session.new(model: "gpt-5.4")
|
|
169
|
+
|
|
170
|
+
session.register_tool(
|
|
171
|
+
"get_weather",
|
|
172
|
+
description: "Get the current weather for a city.",
|
|
173
|
+
parameters: {
|
|
174
|
+
"type" => "object",
|
|
175
|
+
"properties" => {
|
|
176
|
+
"city" => {"type" => "string", "description" => "The city name"}
|
|
177
|
+
},
|
|
178
|
+
"required" => ["city"]
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
session.user("What's the weather in Paris?")
|
|
183
|
+
|
|
184
|
+
loop do
|
|
185
|
+
payload = session.request_payload(:chat_completion)
|
|
186
|
+
response_body = call_api(payload) # Your HTTP call
|
|
187
|
+
response = PromptBuilder::Response.parse(response_body, :chat_completion)
|
|
188
|
+
|
|
189
|
+
session.add_response(response)
|
|
190
|
+
|
|
191
|
+
unless response.has_tool_calls?
|
|
192
|
+
puts response.text
|
|
193
|
+
break
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Invoke tool handlers for each tool call in this response and append the
|
|
197
|
+
# output back to the session before the next iteration.
|
|
198
|
+
response.tool_calls.each do |call|
|
|
199
|
+
result = call_tool(call.name, call.parsed_arguments) # invoke your logic for the tool
|
|
200
|
+
session.add_function_call_output(call_id: call.call_id, result: result.to_s)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The `add_response` method appends the model's output items (messages, tool calls, reasoning, etc.) to the session's conversation history. You add `FunctionCallOutput` items manually after invoking each tool, then loop until the model produces a final text response.
|
|
206
|
+
|
|
207
|
+
### Tool Registry
|
|
208
|
+
|
|
209
|
+
For tools that are shared across multiple sessions, you can use a `ToolRegistry`:
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
registry = PromptBuilder::ToolRegistry.new
|
|
213
|
+
|
|
214
|
+
registry.register(
|
|
215
|
+
"search",
|
|
216
|
+
description: "Search the knowledge base.",
|
|
217
|
+
parameters: {
|
|
218
|
+
"type" => "object",
|
|
219
|
+
"properties" => {
|
|
220
|
+
"query" => {"type" => "string"}
|
|
221
|
+
},
|
|
222
|
+
"required" => ["query"]
|
|
223
|
+
}
|
|
224
|
+
) do |args|
|
|
225
|
+
KnowledgeBase.search(args["query"])
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Apply all tools from the registry to a session
|
|
229
|
+
session = PromptBuilder::Session.new(model: "gpt-5.4")
|
|
230
|
+
session.register_tools(registry)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
There is also a global registry available on the `PromptBuilder` module:
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
PromptBuilder.register_tool("search", description: "Search the knowledge base.") do |args|
|
|
237
|
+
KnowledgeBase.search(args["query"])
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
session.register_tools(PromptBuilder.tool_registry)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Content Types
|
|
244
|
+
|
|
245
|
+
Message content can be a plain string or an array of structured content objects for multi-modal input. Content can be provided as raw Hashes or as `PromptBuilder::Content` objects.
|
|
246
|
+
|
|
247
|
+
**Images**
|
|
248
|
+
|
|
249
|
+
Send an image by URL or as base64-encoded data:
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
# Image from a URL
|
|
253
|
+
session.user([
|
|
254
|
+
PromptBuilder::Content::InputText.new(text: "What is in this image?"),
|
|
255
|
+
PromptBuilder::Content::InputImage.new(url: "https://example.com/photo.jpg")
|
|
256
|
+
])
|
|
257
|
+
|
|
258
|
+
# Image with a detail level hint
|
|
259
|
+
session.user([
|
|
260
|
+
PromptBuilder::Content::InputText.new(text: "Describe this image in detail."),
|
|
261
|
+
PromptBuilder::Content::InputImage.new(
|
|
262
|
+
url: "https://example.com/photo.jpg",
|
|
263
|
+
detail: "high"
|
|
264
|
+
)
|
|
265
|
+
])
|
|
266
|
+
|
|
267
|
+
# Image from raw binary data using a data URL
|
|
268
|
+
session.user([
|
|
269
|
+
PromptBuilder::Content::InputText.new(text: "What is in this image?"),
|
|
270
|
+
PromptBuilder::Content::InputImage.new(
|
|
271
|
+
url: PromptBuilder.data_url(File.binread("photo.png"), "image/png")
|
|
272
|
+
)
|
|
273
|
+
])
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Files**
|
|
277
|
+
|
|
278
|
+
Attach a file by URL or as raw binary data:
|
|
279
|
+
|
|
280
|
+
```ruby
|
|
281
|
+
# File from a URL
|
|
282
|
+
session.user([
|
|
283
|
+
PromptBuilder::Content::InputText.new(text: "Summarize this document."),
|
|
284
|
+
PromptBuilder::Content::InputFile.new(url: "https://example.com/report.pdf")
|
|
285
|
+
])
|
|
286
|
+
|
|
287
|
+
# File from raw binary data with a filename and media type
|
|
288
|
+
session.user([
|
|
289
|
+
PromptBuilder::Content::InputText.new(text: "What does this spreadsheet contain?"),
|
|
290
|
+
PromptBuilder::Content::InputFile.new(
|
|
291
|
+
url: PromptBuilder.data_url(File.binread("data.csv"), "text/csv"),
|
|
292
|
+
filename: "data.csv"
|
|
293
|
+
)
|
|
294
|
+
])
|
|
295
|
+
|
|
296
|
+
# Reference a previously uploaded file by id (OpenAI Files API, Gemini Files API)
|
|
297
|
+
session.user([
|
|
298
|
+
PromptBuilder::Content::InputText.new(text: "Summarize this."),
|
|
299
|
+
PromptBuilder::Content::InputFile.new(file_id: "file_abc123", media_type: "application/pdf")
|
|
300
|
+
])
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Videos**
|
|
304
|
+
|
|
305
|
+
```ruby
|
|
306
|
+
session.user([
|
|
307
|
+
PromptBuilder::Content::InputText.new(text: "Summarize what happens in this video."),
|
|
308
|
+
PromptBuilder::Content::InputVideo.new(url: "https://example.com/clip.mp4")
|
|
309
|
+
])
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**Using Hashes**
|
|
313
|
+
|
|
314
|
+
You can also pass plain Hashes instead of content objects:
|
|
315
|
+
|
|
316
|
+
```ruby
|
|
317
|
+
session.user([
|
|
318
|
+
{"type" => "input_text", "text" => "What is in this image?"},
|
|
319
|
+
{"type" => "input_image", "url" => "https://example.com/photo.jpg"}
|
|
320
|
+
])
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Supported content types**
|
|
324
|
+
|
|
325
|
+
| Type | Class | Description |
|
|
326
|
+
|------|-------|-------------|
|
|
327
|
+
| `input_text` | `Content::InputText` | Text input |
|
|
328
|
+
| `input_image` | `Content::InputImage` | Image input (URL or base64) |
|
|
329
|
+
| `input_file` | `Content::InputFile` | File input (URL or base64) |
|
|
330
|
+
| `input_video` | `Content::InputVideo` | Video input (URL) |
|
|
331
|
+
| `output_text` | `Content::OutputText` | Text output from the model |
|
|
332
|
+
| `refusal` | `Content::RefusalContent` | Refusal content from the model |
|
|
333
|
+
|
|
334
|
+
### Configuration Options
|
|
335
|
+
|
|
336
|
+
Sessions support a wide range of configuration options that map to common API parameters:
|
|
337
|
+
|
|
338
|
+
```ruby
|
|
339
|
+
session = PromptBuilder::Session.new(
|
|
340
|
+
model: "gpt-5.4",
|
|
341
|
+
instructions: "You are a helpful assistant.",
|
|
342
|
+
temperature: 0.7,
|
|
343
|
+
top_p: 0.9,
|
|
344
|
+
max_output_tokens: 1024,
|
|
345
|
+
frequency_penalty: 0.5,
|
|
346
|
+
presence_penalty: 0.5,
|
|
347
|
+
parallel_tool_calls: true,
|
|
348
|
+
reasoning: {"effort" => "high"},
|
|
349
|
+
text: {"format" => {"type" => "json_object"}},
|
|
350
|
+
tool_choice: "auto",
|
|
351
|
+
truncation: "auto",
|
|
352
|
+
prompt_cache_key: "account-123",
|
|
353
|
+
prompt_cache_retention: "24h",
|
|
354
|
+
store: true,
|
|
355
|
+
metadata: {"user_id" => "123"}
|
|
356
|
+
)
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Provider-Specific Extra Parameters
|
|
360
|
+
|
|
361
|
+
The `extra` attribute on sessions, content blocks, and tool definitions lets you pass provider-specific parameters that are not part of the Open Responses canonical format. Each serializer recognizes a defined set of `extra` keys and maps them to the appropriate location in the target format. Unrecognized keys are silently ignored.
|
|
362
|
+
|
|
363
|
+
#### Session Extra
|
|
364
|
+
|
|
365
|
+
Pass provider-specific top-level request parameters via `session.extra`:
|
|
366
|
+
|
|
367
|
+
```ruby
|
|
368
|
+
# Anthropic Messages: top_k, stop_sequences, cache_control
|
|
369
|
+
session = PromptBuilder::Session.new(
|
|
370
|
+
model: "claude-sonnet-4-20250514",
|
|
371
|
+
extra: {
|
|
372
|
+
"top_k" => 40,
|
|
373
|
+
"stop_sequences" => ["\n\nHuman:"],
|
|
374
|
+
"cache_control" => {"type" => "ephemeral"}
|
|
375
|
+
}
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
# OpenAI Chat Completions: stop, seed, logit_bias, n, web_search_options
|
|
379
|
+
session = PromptBuilder::Session.new(
|
|
380
|
+
model: "gpt-4o",
|
|
381
|
+
extra: {
|
|
382
|
+
"stop" => ["END", "\n\n"],
|
|
383
|
+
"seed" => 42,
|
|
384
|
+
"web_search_options" => {"search_context_size" => "medium"}
|
|
385
|
+
}
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# Google Gemini: safety_settings, cached_content, stop_sequences, top_k, seed
|
|
389
|
+
session = PromptBuilder::Session.new(
|
|
390
|
+
model: "gemini-2.0-flash",
|
|
391
|
+
extra: {
|
|
392
|
+
"safety_settings" => [
|
|
393
|
+
{"category" => "HARM_CATEGORY_HATE_SPEECH", "threshold" => "BLOCK_ONLY_HIGH"}
|
|
394
|
+
],
|
|
395
|
+
"cached_content" => "cachedContents/abc123",
|
|
396
|
+
"stop_sequences" => ["END"],
|
|
397
|
+
"top_k" => 40,
|
|
398
|
+
"seed" => 42
|
|
399
|
+
}
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
# Amazon Bedrock Converse: stop_sequences, guardrail_config, additional_model_request_fields
|
|
403
|
+
session = PromptBuilder::Session.new(
|
|
404
|
+
model: "anthropic.claude-v2",
|
|
405
|
+
extra: {
|
|
406
|
+
"stop_sequences" => ["\n\nHuman:"],
|
|
407
|
+
"guardrail_config" => {
|
|
408
|
+
"guardrailIdentifier" => "my-guardrail",
|
|
409
|
+
"guardrailVersion" => "1"
|
|
410
|
+
},
|
|
411
|
+
"additional_model_request_fields" => {"top_k" => 50}
|
|
412
|
+
}
|
|
413
|
+
)
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
#### Content Extra
|
|
417
|
+
|
|
418
|
+
Content blocks support provider-specific attributes via keyword arguments that are captured in the `extra` hash:
|
|
419
|
+
|
|
420
|
+
```ruby
|
|
421
|
+
# Anthropic Messages: cache_control on content blocks for prompt caching
|
|
422
|
+
session.system([
|
|
423
|
+
PromptBuilder::Content::InputText.new(
|
|
424
|
+
text: "You are an assistant with a large knowledge base...",
|
|
425
|
+
cache_control: {"type" => "ephemeral"}
|
|
426
|
+
)
|
|
427
|
+
])
|
|
428
|
+
|
|
429
|
+
# Anthropic Messages: citations opt-in on document blocks
|
|
430
|
+
session.user([
|
|
431
|
+
PromptBuilder::Content::InputFile.new(
|
|
432
|
+
url: "https://example.com/report.pdf",
|
|
433
|
+
citations: {"enabled" => true}
|
|
434
|
+
),
|
|
435
|
+
PromptBuilder::Content::InputText.new(text: "Summarize with citations.")
|
|
436
|
+
])
|
|
437
|
+
|
|
438
|
+
# Bedrock Converse: cache_point markers
|
|
439
|
+
session.user([
|
|
440
|
+
PromptBuilder::Content::InputText.new(
|
|
441
|
+
text: "Long context that should be cached...",
|
|
442
|
+
cache_point: true
|
|
443
|
+
)
|
|
444
|
+
])
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
#### Tool Definition Extra
|
|
448
|
+
|
|
449
|
+
Tool definitions accept provider-specific parameters as keyword arguments:
|
|
450
|
+
|
|
451
|
+
```ruby
|
|
452
|
+
# Anthropic Messages: cache_control on tool definitions
|
|
453
|
+
session.register_tool(
|
|
454
|
+
"search",
|
|
455
|
+
description: "Search the knowledge base.",
|
|
456
|
+
parameters: {"type" => "object", "properties" => {"query" => {"type" => "string"}}},
|
|
457
|
+
cache_control: {"type" => "ephemeral"}
|
|
458
|
+
)
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
#### Recognized Extra Keys by Serializer
|
|
462
|
+
|
|
463
|
+
| Serializer | Session Extra Keys | Content Extra Keys | Tool Extra Keys |
|
|
464
|
+
|:---|:---|:---|:---|
|
|
465
|
+
| **Chat Completions** | `stop`, `seed`, `logit_bias`, `n`, `prediction`, `web_search_options`, `modalities`, `audio` | `file_id`, `media_type` | — |
|
|
466
|
+
| **Messages** | `top_k`, `stop_sequences`, `cache_control` | `cache_control`, `citations`, `file_id`, `media_type` | `cache_control` |
|
|
467
|
+
| **Gemini** | `safety_settings`, `cached_content`, `stop_sequences`, `top_k`, `seed`, `candidate_count`, `response_modalities`, `media_resolution` | `file_id`, `media_type`, `thought_signature` | — |
|
|
468
|
+
| **Converse** | `stop_sequences`, `guardrail_config`, `additional_model_request_fields`, `additional_model_response_field_paths`, `performance_config`, `prompt_variables` | `media_type`, `cache_point` | — |
|
|
469
|
+
|
|
470
|
+
### Serialization and Persistence
|
|
471
|
+
|
|
472
|
+
Sessions and responses can be serialized to and from Hashes for storage or transmission:
|
|
473
|
+
|
|
474
|
+
```ruby
|
|
475
|
+
# Serialize a session to a Hash
|
|
476
|
+
hash = session.to_h
|
|
477
|
+
|
|
478
|
+
# Restore a session from a Hash
|
|
479
|
+
restored_session = PromptBuilder::Session.from_h(hash)
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
This makes it straightforward to persist conversation state in a database or cache between requests.
|
|
483
|
+
|
|
484
|
+
## Serializer Compatibility
|
|
485
|
+
|
|
486
|
+
The Open Responses format is the canonical data model for this gem. When serializing to other formats, some features may not be available because either the target API does not support them or because the Open Responses format does not expose parameters unique to the target API. If you attempt to use a feature that is not supported by a particular serializer, it will be silently omitted from the serialized output.
|
|
487
|
+
|
|
488
|
+
### Session Fields
|
|
489
|
+
|
|
490
|
+
The following table shows which session configuration fields are supported by each serializer. ❌ means the field is silently omitted from the serialized output. Partial support is noted inline.
|
|
491
|
+
|
|
492
|
+
| Session Field | ChatCompletion | Messages | Gemini | Converse |
|
|
493
|
+
|:---|:---:|:---:|:---:|:---:|
|
|
494
|
+
| `background` | ❌ | ❌ | ❌ | ❌ |
|
|
495
|
+
| `frequency_penalty` | ✅ | ❌ | ✅ → `generationConfig.frequencyPenalty` | ❌ |
|
|
496
|
+
| `include` | ❌ | ❌ | ❌ | ❌ |
|
|
497
|
+
| `max_tool_calls` | ❌ | ❌ | ❌ | ❌ |
|
|
498
|
+
| `metadata` | ✅ | `user_id` key only | ❌ | ✅ → `requestMetadata`¹³ |
|
|
499
|
+
| `parallel_tool_calls` | ✅ | ✅ | ❌ | ❌ |
|
|
500
|
+
| `presence_penalty` | ✅ | ❌ | ✅ → `generationConfig.presencePenalty` | ❌ |
|
|
501
|
+
| `prompt_cache_key` | ✅ | ❌ | ❌ | ❌ |
|
|
502
|
+
| `prompt_cache_retention` | ✅ | ❌ | ❌ | ❌ |
|
|
503
|
+
| `reasoning` | `effort` key only | `budget_tokens`/`display`/`effort`/`type` only | `budget_tokens`/`effort`/`summary: "auto"` only | ❌ |
|
|
504
|
+
| `safety_identifier` | ✅ | ✅ → `metadata.user_id` | ❌ | ❌ |
|
|
505
|
+
| `service_tier` | ✅ | `auto`/`standard_only` only | `unspecified`/`standard`/`flex`/`priority` only | ✅ → `serviceTier.type` |
|
|
506
|
+
| `store` | ✅ | ❌ | ✅ | ❌ |
|
|
507
|
+
| `stream` | ✅ | ✅ | endpoint-selected⁸ | ❌ |
|
|
508
|
+
| `stream_options` | `include_usage`/`include_obfuscation` only | ❌ | ❌ | ❌ |
|
|
509
|
+
| `text` | `format`/`verbosity` only | `format.type=json_schema` only | `format` key only | `format.type == json_schema` only |
|
|
510
|
+
| `top_logprobs` | ✅ | ❌ | ✅ → `responseLogprobs`/`logprobs` | ❌ |
|
|
511
|
+
| `truncation` | ❌ | ❌ | ❌ | ❌ |
|
|
512
|
+
|
|
513
|
+
### Content Types
|
|
514
|
+
|
|
515
|
+
| Content Type | ChatCompletion | Messages | Gemini | Converse |
|
|
516
|
+
|:---|:---:|:---:|:---:|:---:|
|
|
517
|
+
| `InputText` | ✅ | ✅ | ✅ | ✅ |
|
|
518
|
+
| `InputImage` | user messages only⁷ | user messages only⁵ | user messages only⁶ | base64 or S3 URI only |
|
|
519
|
+
| `InputFile` | user messages only¹⁰ | user messages only¹ | user messages only⁶ | base64 or S3 URI only² |
|
|
520
|
+
| `InputVideo` | ❌ | ❌ | URL required (Google-hosted URI only)⁶ | S3 URI only |
|
|
521
|
+
| `OutputText` | ✅ (annotations dropped on request)¹¹ | ✅ (annotations ↔ citations)¹² | ✅ | ✅ |
|
|
522
|
+
| `RefusalContent` | dropped⁹ | dropped⁹ | dropped⁹ | dropped⁹ |
|
|
523
|
+
| `Reasoning` items | ❌ | ✅³ | ✅⁴ | ❌ |
|
|
524
|
+
| `Compaction` items | ❌ | ❌ | ❌ | ❌ |
|
|
525
|
+
| `ItemReference` items | ❌ | ❌ | ❌ | ❌ |
|
|
526
|
+
|
|
527
|
+
¹ Messages format uses the media type from the data URL for base64 sources; set `InputFile.media_type` to override. `file_id` is mapped to a `file` source for the Anthropic Files API beta — set the appropriate `anthropic-beta: files-api-2025-04-14` header in your HTTP client.
|
|
528
|
+
² Converse format infers the document type from the filename or URL extension.
|
|
529
|
+
³ Messages format only emits `thinking` blocks that include a cryptographic `signature`; unsigned blocks are silently dropped.
|
|
530
|
+
⁴ Gemini format passes `thoughtSignature` through transparently on reasoning, text, and function-call parts. `redacted_thinking` blocks are silently skipped.
|
|
531
|
+
⁵ Anthropic does not support a `detail` field on images; it is silently dropped. `file_id` is mapped to a `file` source (Anthropic Files API beta).
|
|
532
|
+
⁶ Gemini supports inline base64 data or any URL. For files, set `media_type` or use a recognized `filename`/URL extension.
|
|
533
|
+
⁷ Chat Completions does not accept `InputImage.file_id` — the `image_file` content type is Assistants API only. Use a URL or base64 `data` instead; a `file_id`-only image is omitted.
|
|
534
|
+
⁸ Gemini selects streaming by endpoint (`:streamGenerateContent`), not a request body field, so `session.stream` is silently ignored when serializing to Gemini.
|
|
535
|
+
⁹ `RefusalContent` blocks are dropped silently by all request serializers so a parsed refusal can sit in session history without breaking subsequent `request_payload` calls. A message left empty after stripping is omitted entirely.
|
|
536
|
+
¹⁰ Chat Completions sends `InputFile` as a `{"type": "file", "file": {...}}` content block. `file_id` (Files API) and base64 data URL (with optional `filename`/`media_type`) are supported. A URL-only `InputFile` (non-data URL) is omitted because Chat Completions has no remote-URL form for files.
|
|
537
|
+
¹¹ `OutputText.annotations` (e.g. URL citations from `web_search_options`) are parsed onto the assistant message and round-trip through session history, but are dropped silently on Chat Completions and Converse request serialization since those formats have no request-side equivalent. `OutputText.logprobs` is likewise dropped.
|
|
538
|
+
¹² Messages text-block `citations` are parsed into `OutputText.annotations` and emitted back as `citations` when serializing Messages history. Document/tool-result citation opt-ins are still not modeled.
|
|
539
|
+
¹³ Converse `requestMetadata` only accepts string key/value pairs. This serializer stringifies scalar metadata values and silently omits nested arrays or objects.
|
|
540
|
+
|
|
541
|
+
### Features Not Accessible Through Open Responses
|
|
542
|
+
|
|
543
|
+
These target API features are not available through the Open Responses canonical format because Open Responses does not expose the equivalent parameters:
|
|
544
|
+
|
|
545
|
+
| Feature | ChatCompletion | Messages | Gemini | Converse |
|
|
546
|
+
|:---|:---:|:---:|:---:|:---:|
|
|
547
|
+
| Audio input | ✅ | — | ✅ | — |
|
|
548
|
+
| Audio output / TTS | ✅ | — | ✅ | — |
|
|
549
|
+
| Output `modalities` selection | ✅ | — | ✅ | — |
|
|
550
|
+
| `top_k` sampling | — | ✅ | ✅ | — |
|
|
551
|
+
| `seed` | ✅ | — | ✅ | — |
|
|
552
|
+
| Stop sequences | ✅ | ✅ | ✅ | ✅ |
|
|
553
|
+
| Structured output JSON schema | ✅ | ✅ | ✅ | ✅ |
|
|
554
|
+
| Top-level `cache_control` | — | ✅ | — | — |
|
|
555
|
+
| `logit_bias` | ✅ | — | — | — |
|
|
556
|
+
| Multiple candidates (`n`) | ✅ | — | ✅ (request unsupported; parsing keeps only the first candidate) | — |
|
|
557
|
+
| Speculative decoding (`prediction`) | ✅ | — | — | — |
|
|
558
|
+
| `tool_choice` `allowed_tools` shape | ✅ | — | — | — |
|
|
559
|
+
| Custom (non-function) tool types | ✅ | — | — | — |
|
|
560
|
+
| Strict tool definitions | ✅ | ✅ | ✅ (`VALIDATED` exists, but per-tool `strict` is not supported by this gem) | — |
|
|
561
|
+
| Deprecated Chat `functions` / `function_call` / `max_tokens` / `user` fields | ✅ | — | — | — |
|
|
562
|
+
| Built-in web search (`web_search_options`) | ✅ | ✅ | ✅ | — |
|
|
563
|
+
| Configurable safety thresholds | — | — | ✅ | — |
|
|
564
|
+
| Guardrail policies (`guardrailConfig`, `guardContent`) | — | — | — | ✅ |
|
|
565
|
+
| Cross-region routing (inference profiles) | — | — | — | ✅ |
|
|
566
|
+
| Prompt caching (`cache_control` markers) | — | ✅ | — | — |
|
|
567
|
+
| Prompt caching (`cachePoint` blocks) | — | — | — | ✅ |
|
|
568
|
+
| Citations on documents and tool results | — | ✅ | — | ✅ |
|
|
569
|
+
| `additionalModelRequestFields` / `additionalModelResponseFieldPaths` | — | — | — | ✅ |
|
|
570
|
+
| Bedrock Prompt Management variables (`promptVariables`) | — | — | — | ✅ |
|
|
571
|
+
| `search_result` content blocks | — | ✅ | — | ✅ |
|
|
572
|
+
| Audio content blocks | ✅ | — | ✅ | ✅ |
|
|
573
|
+
| MCP connectors (`mcp_servers`) | — | ✅ | — | — |
|
|
574
|
+
| Code execution `container` reuse | — | ✅ | — | — |
|
|
575
|
+
| Geographic inference routing (`inference_geo`) | — | ✅ | — | — |
|
|
576
|
+
| Beta API headers (`anthropic-beta`, etc.) | — | ✅ | ✅ | — |
|
|
577
|
+
| Built-in code execution | — | ✅ | ✅ | — |
|
|
578
|
+
| Built-in computer use | — | ✅ | ✅ | — |
|
|
579
|
+
| Built-in bash / text editor / memory tools | — | ✅ | — | — |
|
|
580
|
+
| Built-in URL context retrieval (`urlContext`) | — | — | ✅ | — |
|
|
581
|
+
| Built-in Google Maps grounding | — | — | ✅ | — |
|
|
582
|
+
| Built-in semantic file search (`fileSearch`) | — | — | ✅ | — |
|
|
583
|
+
| Context caching (`cachedContent` resource) | — | — | ✅ | — |
|
|
584
|
+
| Tool-call mode `VALIDATED` | — | — | ✅ | — |
|
|
585
|
+
| `toolConfig.retrievalConfig` / `includeServerSideToolInvocations` | — | — | ✅ | — |
|
|
586
|
+
| `responseJsonSchema` (newer JSON Schema variant) | — | — | ✅ | — |
|
|
587
|
+
| `mediaResolution` (image/video token budget) | — | — | ✅ | — |
|
|
588
|
+
| `audioTimestamp`, `speechConfig` | — | — | ✅ | — |
|
|
589
|
+
| `enableEnhancedCivicAnswers` | — | — | ✅ | — |
|
|
590
|
+
| `routingConfig` / `modelSelectionConfig` | — | — | ✅ | — |
|
|
591
|
+
| `thinkingConfig.includeThoughts` detail control | — | — | ✅ (`reasoning.summary: "auto"` only maps to `true`) | — |
|
|
592
|
+
| `thinkingConfig.thinkingLevel` | — | — | ✅ (use `reasoning.effort`) | — |
|
|
593
|
+
| Per-Part `videoMetadata` (offset/FPS) | — | — | ✅ | — |
|
|
594
|
+
| Top-level `labels` (Vertex flavor) | — | — | ✅ | — |
|
|
595
|
+
|
|
596
|
+
For Messages specifically:
|
|
597
|
+
- **Prompt caching** — Anthropic's `cache_control: {"type": "ephemeral"}` markers on system blocks, message content blocks, tool definitions, document blocks, and the top-level request have no Open Responses equivalent. The `prompt_cache_key` and `prompt_cache_retention` session fields are silently omitted from the Messages payload.
|
|
598
|
+
- **Citations** — text-block `citations` are preserved via `OutputText.annotations`. The `citations: {"enabled": true}` opt-in on document blocks and tool results is not modeled by this gem.
|
|
599
|
+
- **Geographic inference routing** — Anthropic's `inference_geo` request field has no canonical Open Responses field. Response-side `usage.inference_geo` is preserved in `response.usage.input_tokens_details`.
|
|
600
|
+
- **API versioning / beta headers** — this gem produces no HTTP, so `anthropic-version`, `anthropic-beta`, and similar headers must be set on your HTTP client. Features behind a beta header (Files API, MCP, code execution containers, extended caching, etc.) still produce valid request payloads through this gem when their request-body shape is supported.
|
|
601
|
+
|
|
602
|
+
### Chat Completions-specific notes
|
|
603
|
+
|
|
604
|
+
Request-side mappings worth calling out:
|
|
605
|
+
|
|
606
|
+
| Canonical field / value | Chat Completions mapping |
|
|
607
|
+
|:---|:---|
|
|
608
|
+
| `instructions` | leading `{"role": "system", "content": ...}` message (use `session.developer(...)` if your model prefers `developer`) |
|
|
609
|
+
| `max_output_tokens` | `max_completion_tokens` |
|
|
610
|
+
| `safety_identifier` | `safety_identifier` (the legacy `user` field is not used) |
|
|
611
|
+
| `prompt_cache_key` / `prompt_cache_retention` | passed through directly |
|
|
612
|
+
| `text.format` | `response_format` (the OR-canonical flat `json_schema` is reshaped into `{"type": "json_schema", "json_schema": {...}}`) |
|
|
613
|
+
| `text.verbosity` | top-level `verbosity` |
|
|
614
|
+
| `reasoning.effort` | `reasoning_effort` |
|
|
615
|
+
| `tool_choice: {"type": "function", "name": ...}` | `{"type": "function", "function": {"name": ...}}` |
|
|
616
|
+
| `InputFile.file_id` | `{"type": "file", "file": {"file_id": ...}}` |
|
|
617
|
+
| `InputFile` with data URL (+ optional `filename`/`media_type`) | `{"type": "file", "file": {"filename": ..., "file_data": "data:<media_type>;base64,..."}}` |
|
|
618
|
+
| `InputImage` with data URL (+ `media_type`) | `{"type": "image_url", "image_url": {"url": "data:<media_type>;base64,..."}}` |
|
|
619
|
+
|
|
620
|
+
Unsupported Chat Completions request features not exposed by this gem include audio input/output (`audio`, `modalities`, `input_audio` content), `web_search_options`, custom tools, `tool_choice: {"type": "allowed_tools", ...}`, `seed`, `stop`, `logit_bias`, `n`, `prediction`, and the deprecated `functions`, `function_call`, `max_tokens`, and `user` fields. Use the modern canonical fields when available (`tools`, `tool_choice`, `max_output_tokens`, `safety_identifier`, `prompt_cache_key`).
|
|
621
|
+
|
|
622
|
+
Response-side behavior and limitations:
|
|
623
|
+
|
|
624
|
+
- Responses with multiple choices (`n > 1`) parse only the first choice; additional candidates are dropped.
|
|
625
|
+
- Streaming chunks (`chat.completion.chunk` deltas) raise `UnsupportedFormatError` — this gem expects a fully assembled non-streaming response body.
|
|
626
|
+
- `system_fingerprint` is exposed on `response.provider_data`.
|
|
627
|
+
- `service_tier` is populated when present on the response.
|
|
628
|
+
- `message.annotations` (URL citations from `web_search_options`) are copied onto `OutputText.annotations`.
|
|
629
|
+
- Audio responses, custom tool calls, legacy `message.function_call`, and unknown response content block types are silently skipped because they have no canonical representation in this gem.
|
|
630
|
+
- `finish_reason` mappings: `stop`/`tool_calls`/`function_call` → `completed`, `length` → `incomplete`, `content_filter` → `failed`. The legacy `function_call` reason is included so older models still surface as completed.
|
|
631
|
+
|
|
632
|
+
### Anthropic Messages-specific mappings
|
|
633
|
+
|
|
634
|
+
A few features map between the canonical Open Responses format and the Messages API in non-obvious ways:
|
|
635
|
+
|
|
636
|
+
| Canonical field / value | Messages mapping |
|
|
637
|
+
|:---|:---|
|
|
638
|
+
| `InputImage.file_id` | `{"type": "image", "source": {"type": "file", "file_id": ...}}` (Files API beta) |
|
|
639
|
+
| `InputFile.file_id` | `{"type": "document", "source": {"type": "file", "file_id": ...}}` (Files API beta) |
|
|
640
|
+
| `FunctionCallOutput.status` ∈ `incomplete`, `failed`, `error` | `tool_result.is_error: true` |
|
|
641
|
+
| `safety_identifier` | `metadata.user_id` |
|
|
642
|
+
| `parallel_tool_calls: false` | `tool_choice.disable_parallel_tool_use: true` (forces `tool_choice.type` to `auto` if unset) |
|
|
643
|
+
| `reasoning.budget_tokens` | `thinking.budget_tokens` (with `thinking.type` defaulted to `enabled`) |
|
|
644
|
+
| `reasoning.effort` | `output_config.effort` (`low`, `medium`, `high`, `xhigh`, or `max`) |
|
|
645
|
+
| `reasoning.type: "adaptive"` | `thinking.type: "adaptive"` |
|
|
646
|
+
| `text.format.type: "json_schema"` | `output_config.format` (`type` and `schema` only; `name`, `description`, and `strict` are ignored) |
|
|
647
|
+
| `Tools::Definition#strict` | tool definition `strict: true` |
|
|
648
|
+
| `tool_choice: "required"` | `{"type": "any"}` |
|
|
649
|
+
| `tool_choice: {"type": "function", "name": ...}` | `{"type": "tool", "name": ...}` |
|
|
650
|
+
|
|
651
|
+
Response stop reasons are mapped to Open Responses statuses as follows: `end_turn`, `tool_use`, `stop_sequence`, and `pause_turn` → `completed`; `max_tokens` → `incomplete`; `refusal` → `failed`. When `stop_sequence` is matched, the matched text is exposed via `response.incomplete_details["stop_sequence"]`. Anthropic `stop_details` and response `container` metadata are exposed via `response.incomplete_details`. Additional usage details (`cache_creation` breakdown, `service_tier`, `inference_geo`, `server_tool_use`, and cache token counts) are surfaced through `response.usage.input_tokens_details`.
|
|
652
|
+
|
|
653
|
+
Built-in tool response content blocks (`server_tool_use`, `web_search_tool_result`, `web_fetch_tool_result`, `code_execution_tool_result`, `bash_code_execution_tool_result`, `tool_search_tool_result`, `mcp_tool_use`, `mcp_tool_result`, `container_upload`, `search_result`) are silently skipped on parse, since this gem has no canonical representation for them.
|
|
654
|
+
|
|
655
|
+
### Gemini-specific notes
|
|
656
|
+
|
|
657
|
+
Request-side mappings worth calling out:
|
|
658
|
+
|
|
659
|
+
| Canonical field / value | Gemini mapping |
|
|
660
|
+
|:---|:---|
|
|
661
|
+
| `instructions` + `system`/`developer` messages | merged into `systemInstruction.parts[]` |
|
|
662
|
+
| `max_output_tokens` | `generationConfig.maxOutputTokens` |
|
|
663
|
+
| `temperature` / `top_p` | `generationConfig.temperature` / `topP` |
|
|
664
|
+
| `presence_penalty` / `frequency_penalty` | `generationConfig.presencePenalty` / `frequencyPenalty` |
|
|
665
|
+
| `top_logprobs: N` | `generationConfig.{responseLogprobs: true, logprobs: N}` |
|
|
666
|
+
| `text.format == "text"` | `generationConfig.responseMimeType = "text/plain"` |
|
|
667
|
+
| `text.format == "json_object"` | `generationConfig.responseMimeType = "application/json"` |
|
|
668
|
+
| `text.format == "json_schema"` | `generationConfig.responseMimeType = "application/json"` + `responseSchema` (read from `format.json_schema.schema` or `format.schema`) |
|
|
669
|
+
| `reasoning.budget_tokens` | `generationConfig.thinkingConfig.thinkingBudget` |
|
|
670
|
+
| `tool_choice: "auto"` / `"none"` / `"required"` | `toolConfig.functionCallingConfig.mode = AUTO` / `NONE` / `ANY` |
|
|
671
|
+
| `tool_choice: {"type": "function", "name": ...}` | `mode = ANY` + `allowedFunctionNames: [name]` |
|
|
672
|
+
| Tool definitions | single `tools[0].functionDeclarations[]` entry |
|
|
673
|
+
|
|
674
|
+
Content and message restrictions:
|
|
675
|
+
|
|
676
|
+
- Assistant messages map to `role: "model"`; consecutive same-role turns are merged automatically.
|
|
677
|
+
- `InputImage`, `InputFile`, and `InputVideo` are only supported in user messages; the same content on an assistant message is omitted.
|
|
678
|
+
- `InputImage` accepts any URL, a base64 data URL (with required `media_type`), or a Files API `file_id`.
|
|
679
|
+
- `InputFile` accepts any URL, a base64 data URL, or `file_id`. `media_type` is required when not inferable from a `filename` or URL extension. Recognized extensions: `pdf`, `txt`, `md`/`markdown`, `html`/`htm`, `csv`, `json`, `xml`, `rtf`.
|
|
680
|
+
- `InputVideo` requires a URL; raw bytes are not modeled.
|
|
681
|
+
- `RefusalContent` is dropped silently so a parsed Chat Completions refusal can sit in session history without breaking subsequent serialization.
|
|
682
|
+
- `Reasoning` items round-trip via `parts[].thought` with `thoughtSignature` preserved. `redacted_thinking`, `summary`, and unknown reasoning block types are silently skipped.
|
|
683
|
+
- `FunctionCall.arguments` must parse to a JSON object — Gemini's `functionCall.args` is a Struct.
|
|
684
|
+
- `FunctionCallOutput` content must be text-only (`InputText`/`OutputText` or a plain string); other content types are omitted. When `output` parses to a JSON object it is forwarded as the `functionResponse.response` Struct; otherwise it is wrapped as `{"result": ...}`. The output must reference a prior `FunctionCall` so its `name` can be resolved (Gemini's `functionResponse.name` is the tool name, not the call id).
|
|
685
|
+
- `Compaction` and `ItemReference` items are silently skipped.
|
|
686
|
+
- `tool_choice` without registered tools is omitted (along with unsupported `tool_choice` shapes).
|
|
687
|
+
|
|
688
|
+
Response-side limitations:
|
|
689
|
+
|
|
690
|
+
- Unknown response `Part` shapes (`inlineData`, `fileData`, `executableCode`, `codeExecutionResult`, `videoMetadata`, server-side `toolCall`/`toolResponse`, or any `Part` without a recognized content key) are silently skipped.
|
|
691
|
+
- Only `candidates[0]` is parsed. When `candidateCount > 1`, additional candidates are dropped (their index is preserved on `provider_data`).
|
|
692
|
+
- Function-call `id` from the response is dropped; the parser synthesizes `gemini_call_<seed>_<n>` so multiple calls in one response share a deterministic seed.
|
|
693
|
+
- `finishReason` mappings: `STOP` → `completed`; `MAX_TOKENS` → `incomplete`; `SAFETY`, `RECITATION`, `OTHER`, `BLOCKLIST`, `PROHIBITED_CONTENT`, `SPII`, `MALFORMED_FUNCTION_CALL`, `IMAGE_SAFETY`, `LANGUAGE`, `UNEXPECTED_TOOL_CALL`, `TOO_MANY_TOOL_CALLS`, `MODEL_ARMOR` → `failed`. `FINISH_REASON_UNSPECIFIED` is treated as nil.
|
|
694
|
+
- An empty `candidates` array combined with `promptFeedback.blockReason` is mapped to `failed`.
|
|
695
|
+
- `usageMetadata.cachedContentTokenCount`, `toolUsePromptTokenCount`, `promptTokensDetails`, `cacheTokensDetails`, and `toolUsePromptTokensDetails` populate `response.usage.input_tokens_details`. `thoughtsTokenCount` and `candidatesTokensDetails` populate `response.usage.output_tokens_details`.
|
|
696
|
+
- Response metadata with no canonical Open Responses slot is exposed on `response.provider_data`: `groundingMetadata`, `citationMetadata`, `urlContextMetadata`, `urlRetrievalMetadata`, `safetyRatings`, `groundingAttributions`, `avgLogprobs`, `logprobsResult`, `finishMessage`, candidate `index`, top-level `createTime`, and full `promptFeedback`. Streaming chunks are not parsed — this gem expects a fully assembled non-streaming response body.
|
|
697
|
+
|
|
698
|
+
### Converse-specific notes
|
|
699
|
+
|
|
700
|
+
Request-side restrictions worth calling out:
|
|
701
|
+
|
|
702
|
+
| Canonical field / value | Converse mapping |
|
|
703
|
+
|:---|:---|
|
|
704
|
+
| `instructions` + `system`/`developer` messages | merged into top-level `system` array |
|
|
705
|
+
| `max_output_tokens` | `inferenceConfig.maxTokens` |
|
|
706
|
+
| `temperature` / `top_p` | `inferenceConfig.temperature` / `inferenceConfig.topP` |
|
|
707
|
+
| `metadata` | `requestMetadata` with scalar values stringified |
|
|
708
|
+
| `service_tier` | `serviceTier.type` |
|
|
709
|
+
| `text.format.type == "json_schema"` | `outputConfig.textFormat` with `structure.jsonSchema` |
|
|
710
|
+
| `tool_choice: "auto"` | `{"auto": {}}` |
|
|
711
|
+
| `tool_choice: "required"` | `{"any": {}}` |
|
|
712
|
+
| `tool_choice: {"type": "function", "name": ...}` | `{"tool": {"name": ...}}` |
|
|
713
|
+
|
|
714
|
+
Content and message restrictions:
|
|
715
|
+
|
|
716
|
+
- The first message must have role `user`; the request raises if it doesn't. Consecutive same-role messages are merged automatically.
|
|
717
|
+
- `system` and `developer` messages must contain text only. Non-text content is omitted because Converse system blocks only map to `text`, `guardContent`, or `cachePoint`, and the latter two are not modeled by this gem.
|
|
718
|
+
- `InputImage` requires either base64 `data` or an `s3://` URI; content with an arbitrary public URL is omitted. `file_id` and `detail` have no Converse equivalent and are silently ignored.
|
|
719
|
+
- `InputFile` requires either a base64 data URL or an `s3://` URI; document `name` is auto-derived from `filename` / URL and sanitized to Bedrock's allowed character set (`[A-Za-z0-9 \-()\[\]]{1,256}`), with collisions disambiguated within a single request.
|
|
720
|
+
- `InputFile.file_id` is silently ignored because Converse does not accept provider file IDs for documents.
|
|
721
|
+
- `InputVideo` requires an `s3://` URI; a non-S3 URL is omitted, and raw bytes (`source.bytes`) cannot be sent because the canonical `InputVideo` content type does not model byte data.
|
|
722
|
+
- `InputImage`, `InputFile`, and `InputVideo` are only supported in user messages; the same content on an assistant message is omitted. `RefusalContent`, `OutputText.annotations`, and `OutputText.logprobs` are also dropped because Converse has no request-side representation for them.
|
|
723
|
+
- `Reasoning` items are silently skipped on the request side, so multi-turn extended-thinking + tool-use loops are not supported through this serializer (the response parser does decode `reasoningContent` blocks back into `Reasoning` items, but they cannot be sent back).
|
|
724
|
+
- `Compaction` and `ItemReference` items are silently skipped.
|
|
725
|
+
- `FunctionCall.arguments` must parse to a JSON object; non-object JSON values raise (Bedrock's `toolUse.input` requires an object).
|
|
726
|
+
- `FunctionCallOutput.output` content supports `InputText`/`OutputText`, `InputImage`, `InputFile`, and `InputVideo`, mapping to Converse `text`, `image`, `document`, and `video` tool result blocks. Converse also supports `json` and `searchResult` tool result blocks, but this gem has no canonical content types for them, so unsupported content is omitted. `FunctionCallOutput.status` is mapped: `completed` → `success`, `failed`/`incomplete` → `error`, anything else passes through or is dropped.
|
|
727
|
+
- `tool_choice: "none"` and `tool_choice` without registered tools are both omitted.
|
|
728
|
+
- Converse API request features not exposed by this gem include `stopSequences`, `additionalModelRequestFields`, `additionalModelResponseFieldPaths`, `guardrailConfig` / `guardContent`, `cachePoint`, Prompt Management `promptVariables`, audio blocks, `searchResult` blocks, and `performanceConfig.latency`.
|
|
729
|
+
|
|
730
|
+
Response-side limitations:
|
|
731
|
+
|
|
732
|
+
- Unknown content block keys (e.g. `citationsContent`, `guardContent`) are silently skipped.
|
|
733
|
+
- `metrics.latencyMs`, `trace` (guardrail and prompt-router trace events), `additionalModelResponseFields`, `performanceConfig`, and the raw `serviceTier` object are exposed on `response.provider_data`. `response.service_tier` is also populated from `serviceTier.type`.
|
|
734
|
+
- `stopReason` mappings: `end_turn` / `tool_use` / `stop_sequence` → `completed`, `max_tokens` / `model_context_window_exceeded` → `incomplete`, `guardrail_intervened` / `content_filtered` / `malformed_model_output` / `malformed_tool_use` → `failed`. Unlike the Messages serializer, the matched stop sequence text is not surfaced separately because Converse does not echo it back unless you request provider-specific fields through `additionalModelResponseFieldPaths`, which this gem cannot emit.
|
|
735
|
+
- `usage.cacheReadInputTokens` and `usage.cacheWriteInputTokens` populate `response.usage.input_tokens_details["cached_tokens"]` and `["cache_creation_input_tokens"]`. Cache writes still require `cachePoint` markers in the request, which this gem cannot produce.
|
|
736
|
+
|
|
737
|
+
## Installation
|
|
738
|
+
|
|
739
|
+
Add this line to your application's Gemfile:
|
|
740
|
+
|
|
741
|
+
```ruby
|
|
742
|
+
gem "prompt_builder"
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
Then execute:
|
|
746
|
+
```bash
|
|
747
|
+
$ bundle
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
Or install it yourself as:
|
|
751
|
+
```bash
|
|
752
|
+
$ gem install prompt_builder
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
## Contributing
|
|
756
|
+
|
|
757
|
+
Open a pull request on [GitHub](https://github.com/bdurand/prompt_builder).
|
|
758
|
+
|
|
759
|
+
Please use the [standardrb](https://github.com/testdouble/standard) syntax and lint your code with `standardrb --fix` before submitting.
|
|
760
|
+
|
|
761
|
+
## License
|
|
762
|
+
|
|
763
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|