raix-bedrock 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 +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +281 -0
- data/Rakefile +12 -0
- data/lib/raix/bedrock/openai_proxy.rb +17 -0
- data/lib/raix/bedrock/provider.rb +287 -0
- data/lib/raix/bedrock/version.rb +7 -0
- data/lib/raix/bedrock.rb +11 -0
- data/sig/raix/bedrock.rbs +6 -0
- metadata +81 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0a127460347edbf04426ee850a2cf0553f09c43fdda9888c589053d302869efa
|
4
|
+
data.tar.gz: e2209b270ead4e6015591d3657a883e7b0b57e6aceb90769bc3a6da5552f7a6b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 79d2080315f2b52efe14b187532aa56ae796ccb3ab086ac110314ade1167b6cff40aa1a7dca7642282fd1cedca54f87cf71ed9fe3979b3ce60aa05c770388a8a
|
7
|
+
data.tar.gz: b6a290017981cd3f911d156ea9194c111986d3fcb5e4a2a5253d8f8ac68172c57a63e4af04375c802253a70876ed28b21ce29d1af1cda37d6ce352c4ce5c2784
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 Martin Emde
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,281 @@
|
|
1
|
+
# Raix::Bedrock
|
2
|
+
|
3
|
+
AWS Bedrock provider for the [Raix](https://github.com/OlympiaAI/raix) AI framework. This gem allows you to use AWS Bedrock's language models (including Anthropic Claude) with Raix's powerful features like function calling, prompt management, and MCP (Model Context Protocol) support.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- **Integration**: Works as a drop-in replacement for OpenAI in Raix applications
|
8
|
+
- **Compatibility**: Aims for full support of Raix features including function dispatch and MCP
|
9
|
+
- **Message Format Translation**: Automatically converts between OpenAI and Bedrock message formats
|
10
|
+
- **Tool Support**: Full support for function/tool calling with compatible models
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem "raix"
|
18
|
+
gem "raix-bedrock"
|
19
|
+
```
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
```bash
|
24
|
+
bundle install
|
25
|
+
```
|
26
|
+
|
27
|
+
Or install it yourself as:
|
28
|
+
|
29
|
+
```bash
|
30
|
+
gem install raix-bedrock
|
31
|
+
```
|
32
|
+
|
33
|
+
## Configuration
|
34
|
+
|
35
|
+
### Prerequisites
|
36
|
+
|
37
|
+
Ensure you have AWS credentials configured. The gem uses the standard AWS SDK authentication chain:
|
38
|
+
- Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`)
|
39
|
+
- AWS IAM roles
|
40
|
+
- AWS configuration files
|
41
|
+
|
42
|
+
### Setup Options
|
43
|
+
|
44
|
+
There are two ways to configure Raix with Bedrock:
|
45
|
+
|
46
|
+
#### Option 1: Use as the openai_client in your configuration
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
require "raix"
|
50
|
+
require "raix/bedrock"
|
51
|
+
|
52
|
+
Raix.configure do |config|
|
53
|
+
config.openai_client = Raix::Bedrock::Provider.new(
|
54
|
+
region: "us-west-2", # Optional, defaults to AWS_REGION env var
|
55
|
+
model: "us.anthropic.claude-sonnet-4-20250514-v1:0" # Optional default model
|
56
|
+
)
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
#### Option 2: Using the To-Be-Merged provider interface
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
require "raix"
|
64
|
+
require "raix/bedrock"
|
65
|
+
|
66
|
+
Raix.configure do |config|
|
67
|
+
config.register_provider :bedrock, Raix::Bedrock::OpenAIProxy.new(
|
68
|
+
region: "us-west-2",
|
69
|
+
model: "us.anthropic.claude-sonnet-4-20250514-v1:0"
|
70
|
+
)
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
Both options provide identical functionality. The OpenAI proxy ensures compatibility with existing versions of Raix that don't support registering different providers.
|
75
|
+
|
76
|
+
## Usage
|
77
|
+
|
78
|
+
### Simple Chat
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
class ChatAssistant
|
82
|
+
include Raix::ChatCompletion
|
83
|
+
|
84
|
+
def initialize
|
85
|
+
@model = "us.anthropic.claude-sonnet-4-20250514-v1:0"
|
86
|
+
@system = "You are a helpful assistant."
|
87
|
+
end
|
88
|
+
|
89
|
+
def ask(question)
|
90
|
+
chat_completion(question)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
assistant = ChatAssistant.new
|
95
|
+
response = assistant.ask("What is the capital of France?")
|
96
|
+
puts response # => "The capital of France is Paris."
|
97
|
+
```
|
98
|
+
|
99
|
+
### With Function Calling
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
class WeatherAssistant
|
103
|
+
include Raix::ChatCompletion
|
104
|
+
include Raix::FunctionDispatch
|
105
|
+
|
106
|
+
def initialize
|
107
|
+
# Use a model that supports tools
|
108
|
+
@model = "us.anthropic.claude-3-7-sonnet-20250219-v1:0"
|
109
|
+
@system = "You are a weather assistant."
|
110
|
+
end
|
111
|
+
|
112
|
+
function(
|
113
|
+
:get_weather,
|
114
|
+
"Get the current weather for a location",
|
115
|
+
location: {
|
116
|
+
type: "string",
|
117
|
+
description: "The city and state, e.g. San Francisco, CA",
|
118
|
+
required: true
|
119
|
+
},
|
120
|
+
unit: {
|
121
|
+
type: "string",
|
122
|
+
enum: ["celsius", "fahrenheit"],
|
123
|
+
description: "Temperature unit"
|
124
|
+
}
|
125
|
+
) do |args|
|
126
|
+
# Your weather API implementation here
|
127
|
+
location = args[:location]
|
128
|
+
unit = args[:unit] || "fahrenheit"
|
129
|
+
|
130
|
+
# Mock response
|
131
|
+
{
|
132
|
+
location: location,
|
133
|
+
temperature: 72,
|
134
|
+
unit: unit,
|
135
|
+
condition: "sunny"
|
136
|
+
}.to_json
|
137
|
+
end
|
138
|
+
|
139
|
+
def ask(question)
|
140
|
+
chat(question)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
assistant = WeatherAssistant.new
|
145
|
+
response = assistant.ask("What's the weather in San Francisco?")
|
146
|
+
```
|
147
|
+
|
148
|
+
### Direct Provider Usage
|
149
|
+
|
150
|
+
*You shouldn't do this, just use aws-sdk-bedrockruntime, but it's interesting that Claude generated this example, so I'll leave it.*
|
151
|
+
|
152
|
+
You can also use the provider directly without Raix's abstractions:
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
provider = Raix::Bedrock::Provider.new
|
156
|
+
|
157
|
+
result = provider.chat(parameters: {
|
158
|
+
model: "us.anthropic.claude-sonnet-4-20250514-v1:0",
|
159
|
+
messages: [
|
160
|
+
{ role: "system", content: "You are a helpful assistant." },
|
161
|
+
{ role: "user", content: "Tell me a joke." }
|
162
|
+
],
|
163
|
+
temperature: 0.7,
|
164
|
+
max_completion_tokens: 100
|
165
|
+
})
|
166
|
+
|
167
|
+
puts result.dig("choices", 0, "message", "content")
|
168
|
+
```
|
169
|
+
|
170
|
+
### MCP (Model Context Protocol) Integration
|
171
|
+
|
172
|
+
The Bedrock provider works seamlessly with Raix's MCP support:
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
class MCPAssistant
|
176
|
+
include Raix::ChatCompletion
|
177
|
+
include Raix::MCP
|
178
|
+
|
179
|
+
client = Raix::MCP::StdioClient.new(
|
180
|
+
"npx",
|
181
|
+
"-y",
|
182
|
+
"@modelcontextprotocol/server-github",
|
183
|
+
{ "GITHUB_PERSONAL_ACCESS_TOKEN" => ENV["GITHUB_TOKEN"] }
|
184
|
+
)
|
185
|
+
|
186
|
+
# Register only the specific GitHub tools we want
|
187
|
+
mcp(
|
188
|
+
client: client,
|
189
|
+
only: %i[
|
190
|
+
search_repositories
|
191
|
+
get_repository
|
192
|
+
get_issue
|
193
|
+
list_issues
|
194
|
+
create_issue
|
195
|
+
create_pull_request
|
196
|
+
search_code
|
197
|
+
]
|
198
|
+
)
|
199
|
+
|
200
|
+
def initialize
|
201
|
+
@model = "us.anthropic.claude-3-7-sonnet-20250219-v1:0"
|
202
|
+
@system = "You are an assistant with access to MCP tools."
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# MCP servers provide tools automatically
|
207
|
+
assistant = MCPAssistant.new
|
208
|
+
response = assistant.chat("Search for open issues in rails/rails repository")
|
209
|
+
```
|
210
|
+
|
211
|
+
## Model Selection
|
212
|
+
|
213
|
+
### Recommended Models
|
214
|
+
|
215
|
+
- **For general use**: `us.anthropic.claude-sonnet-4-20250514-v1:0`
|
216
|
+
- Best overall performance and capabilities
|
217
|
+
|
218
|
+
- **For tool/function usage**: `us.anthropic.claude-3-7-sonnet-20250219-v1:0`
|
219
|
+
- Full support for function calling and tools
|
220
|
+
- Slightly older model but with complete tool support
|
221
|
+
|
222
|
+
- **Older without tool calls**: `anthropic.claude-3-5-sonnet-20241022-v2:0`
|
223
|
+
- Good for less complex tasks
|
224
|
+
- Doesn't support tool calls
|
225
|
+
`
|
226
|
+
### Model Format
|
227
|
+
|
228
|
+
Use inference profile IDs (e.g., `us.anthropic.claude-*`) for better performance and availability rather than base model IDs.
|
229
|
+
|
230
|
+
## Limitations
|
231
|
+
|
232
|
+
- **No Streaming Support**: The Bedrock Converse API doesn't currently support streaming responses
|
233
|
+
- **Token Limits**: Maximum tokens are clamped to 8,192 to ensure compatibility
|
234
|
+
- **Model-Specific Features**: Some models may have specific requirements or limitations
|
235
|
+
|
236
|
+
## Examples
|
237
|
+
|
238
|
+
The `examples/` directory contains complete working examples:
|
239
|
+
|
240
|
+
- `simple_chat.rb` - Basic chat functionality and tool usage
|
241
|
+
- `mcp_integration.rb` - MCP server integration with Bedrock
|
242
|
+
- `github_mcp/` - Complete GitHub MCP integration example
|
243
|
+
|
244
|
+
To run an example:
|
245
|
+
|
246
|
+
```bash
|
247
|
+
cd examples
|
248
|
+
ruby simple_chat.rb
|
249
|
+
```
|
250
|
+
|
251
|
+
## Development
|
252
|
+
|
253
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
254
|
+
|
255
|
+
### Running Tests
|
256
|
+
|
257
|
+
```bash
|
258
|
+
bundle exec rspec
|
259
|
+
```
|
260
|
+
|
261
|
+
### Console
|
262
|
+
|
263
|
+
```bash
|
264
|
+
bin/console
|
265
|
+
```
|
266
|
+
|
267
|
+
This will give you an interactive prompt with the gem loaded.
|
268
|
+
|
269
|
+
## Contributing
|
270
|
+
|
271
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/martinemde/raix-bedrock. This project is intended to be a safe, welcoming space for collaboration.
|
272
|
+
|
273
|
+
1. Fork it
|
274
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
275
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
276
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
277
|
+
5. Create a new Pull Request
|
278
|
+
|
279
|
+
## License
|
280
|
+
|
281
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Raix
|
4
|
+
module Bedrock
|
5
|
+
# A proxy class that allows Raix to use the Bedrock provider as an OpenAI client.
|
6
|
+
# This provides backward compatibility if the provider interface is not accepted upstream.
|
7
|
+
class OpenAIProxy
|
8
|
+
def initialize(provider = nil, **provider_options)
|
9
|
+
@provider = provider || Provider.new(**provider_options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def chat(parameters:)
|
13
|
+
@provider.chat(parameters: parameters)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,287 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "securerandom"
|
5
|
+
require "aws-sdk-bedrockruntime"
|
6
|
+
|
7
|
+
module Raix
|
8
|
+
module Bedrock
|
9
|
+
# Bedrock provider for Raix.
|
10
|
+
class Provider
|
11
|
+
attr_reader :client
|
12
|
+
|
13
|
+
def initialize(
|
14
|
+
# Prefer inference profile IDs for models that don't support on-demand throughput
|
15
|
+
model: "us.anthropic.claude-sonnet-4-20250514-v1:0",
|
16
|
+
# Alternative with tools: "us.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
17
|
+
region: ENV["AWS_REGION"] || ENV["AMAZON_REGION"] || ENV["AWS_DEFAULT_REGION"] || "us-east-1"
|
18
|
+
)
|
19
|
+
@model = model
|
20
|
+
@client = Aws::BedrockRuntime::Client.new(region: region)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Support for configure.openai_client = Raix::Bedrock::Provider.new(...)
|
24
|
+
# You must tell Raix to use openai in your configuration.
|
25
|
+
def chat(parameters:)
|
26
|
+
# Extract model and messages from the parameters hash
|
27
|
+
# These were merged in by the caller
|
28
|
+
params = parameters.dup
|
29
|
+
model = params.delete(:model)
|
30
|
+
messages = params.delete(:messages)
|
31
|
+
|
32
|
+
# The caller has already modified params:
|
33
|
+
# - Converted max_tokens to max_completion_tokens (unless prediction mode)
|
34
|
+
# - Added stream_options if streaming
|
35
|
+
# - Removed temperature for certain models
|
36
|
+
# We just pass these through as-is since they're already in the expected format
|
37
|
+
|
38
|
+
# Call the main request method
|
39
|
+
request(params: params, model: model, messages: messages)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Support for configure.register_provider(:bedrock, Raix::Bedrock::Provider.new(...))
|
43
|
+
def request(params:, model:, messages:)
|
44
|
+
params = params.dup
|
45
|
+
|
46
|
+
# Remove streaming option - Bedrock Converse API doesn't support streaming
|
47
|
+
params.delete(:stream)
|
48
|
+
|
49
|
+
# Convert OpenAI messages to Bedrock format
|
50
|
+
system_blocks = []
|
51
|
+
convo_messages = []
|
52
|
+
|
53
|
+
messages.each do |m|
|
54
|
+
role = (m[:role] || m["role"]).to_s
|
55
|
+
content = m[:content] || m["content"]
|
56
|
+
|
57
|
+
# OpenAI tool result messages → Bedrock tool_result on a user message
|
58
|
+
if role == "tool"
|
59
|
+
tool_call_id = m[:tool_call_id] || m["tool_call_id"]
|
60
|
+
next unless tool_call_id
|
61
|
+
|
62
|
+
tool_text = content.to_s.strip
|
63
|
+
convo_messages << {
|
64
|
+
role: "user",
|
65
|
+
content: [
|
66
|
+
{
|
67
|
+
tool_result: {
|
68
|
+
tool_use_id: tool_call_id,
|
69
|
+
content: tool_text.empty? ? [] : [{ text: tool_text }],
|
70
|
+
status: "success"
|
71
|
+
}
|
72
|
+
}
|
73
|
+
]
|
74
|
+
}
|
75
|
+
next
|
76
|
+
end
|
77
|
+
|
78
|
+
case role
|
79
|
+
when "system"
|
80
|
+
sys_text = content.to_s.strip
|
81
|
+
system_blocks << { text: sys_text } unless sys_text.empty?
|
82
|
+
when "user"
|
83
|
+
user_text = content.to_s.strip
|
84
|
+
next if user_text.empty?
|
85
|
+
|
86
|
+
convo_messages << { role: "user", content: [{ text: user_text }] }
|
87
|
+
when "assistant"
|
88
|
+
blocks = []
|
89
|
+
# Add text content first if present
|
90
|
+
asst_text = content.to_s.strip
|
91
|
+
blocks << { text: asst_text } unless asst_text.empty?
|
92
|
+
|
93
|
+
# Then add tool_calls
|
94
|
+
tool_calls = m[:tool_calls] || m["tool_calls"]
|
95
|
+
if tool_calls.is_a?(Array)
|
96
|
+
tool_calls.each do |tc|
|
97
|
+
fn = tc[:function] || tc["function"] || {}
|
98
|
+
name = fn[:name] || fn["name"]
|
99
|
+
next unless name
|
100
|
+
|
101
|
+
args = fn[:arguments] || fn["arguments"] || "{}"
|
102
|
+
input_obj = begin
|
103
|
+
args.is_a?(String) ? JSON.parse(args) : args
|
104
|
+
rescue StandardError
|
105
|
+
{}
|
106
|
+
end
|
107
|
+
tu_id = tc[:id] || tc["id"] || SecureRandom.uuid
|
108
|
+
blocks << { tool_use: { tool_use_id: tu_id, name: name, input: input_obj } }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
convo_messages << { role: "assistant", content: blocks } if blocks.any?
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Normalize messages
|
116
|
+
normalized_messages = normalize_messages(convo_messages)
|
117
|
+
|
118
|
+
# Build Bedrock parameters
|
119
|
+
bedrock_params = {
|
120
|
+
model_id: model || @model,
|
121
|
+
messages: normalized_messages
|
122
|
+
}
|
123
|
+
bedrock_params[:system] = system_blocks unless system_blocks.empty?
|
124
|
+
|
125
|
+
# Map inference config
|
126
|
+
inference_config = build_inference_config(params)
|
127
|
+
bedrock_params[:inference_config] = inference_config unless inference_config.empty?
|
128
|
+
|
129
|
+
# Map tools if provided
|
130
|
+
if params[:tools].is_a?(Array) && !params[:tools].empty?
|
131
|
+
tool_config = build_tool_config(params[:tools], params[:tool_choice])
|
132
|
+
bedrock_params[:tool_config] = tool_config if tool_config
|
133
|
+
end
|
134
|
+
|
135
|
+
# Make the Bedrock API call
|
136
|
+
response = @client.converse(bedrock_params)
|
137
|
+
|
138
|
+
# Convert Bedrock response to OpenAI format
|
139
|
+
convert_response(response)
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
def normalize_messages(messages)
|
145
|
+
messages.filter_map do |msg|
|
146
|
+
blocks = (msg[:content] || []).filter_map do |blk|
|
147
|
+
if blk[:text]
|
148
|
+
txt = blk[:text].to_s.strip
|
149
|
+
next nil if txt.empty?
|
150
|
+
|
151
|
+
{ text: txt }
|
152
|
+
elsif blk[:tool_use]
|
153
|
+
blk
|
154
|
+
elsif blk[:tool_result]
|
155
|
+
normalize_tool_result(blk[:tool_result])
|
156
|
+
end
|
157
|
+
end
|
158
|
+
next if blocks.empty?
|
159
|
+
|
160
|
+
{ role: msg[:role], content: blocks }
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def normalize_tool_result(tool_result)
|
165
|
+
content = (tool_result[:content] || []).filter_map do |c|
|
166
|
+
if c[:text]
|
167
|
+
t = c[:text].to_s.strip
|
168
|
+
next nil if t.empty?
|
169
|
+
|
170
|
+
{ text: t }
|
171
|
+
else
|
172
|
+
c
|
173
|
+
end
|
174
|
+
end
|
175
|
+
# Ensure at least one content block exists
|
176
|
+
content = [{ json: {} }] if content.empty?
|
177
|
+
{ tool_result: tool_result.merge(content: content) }
|
178
|
+
end
|
179
|
+
|
180
|
+
def build_inference_config(params)
|
181
|
+
config = {}
|
182
|
+
# Clamp max tokens to model limits; default sensibly if absent
|
183
|
+
max_tokens = params[:max_tokens] || params[:max_completion_tokens] || 1024
|
184
|
+
max_tokens = [max_tokens.to_i, 8192].min
|
185
|
+
|
186
|
+
config[:temperature] = params[:temperature] if params[:temperature]
|
187
|
+
config[:max_tokens] = max_tokens
|
188
|
+
config[:top_p] = params[:top_p] if params[:top_p]
|
189
|
+
config
|
190
|
+
end
|
191
|
+
|
192
|
+
def build_tool_config(tools, tool_choice)
|
193
|
+
bedrock_tools = tools.filter_map do |t|
|
194
|
+
next unless (t[:type] || t["type"]).to_s == "function"
|
195
|
+
|
196
|
+
fn = t[:function] || t["function"] || {}
|
197
|
+
name = fn[:name] || fn["name"]
|
198
|
+
next unless name
|
199
|
+
|
200
|
+
desc = fn[:description] || fn["description"] || ""
|
201
|
+
schema = fn[:parameters] || fn["parameters"] || {}
|
202
|
+
# Convert schema to use string keys for AWS compatibility
|
203
|
+
schema_json = JSON.parse(JSON.generate(schema))
|
204
|
+
{ tool_spec: { name: name.to_s, description: desc, input_schema: { json: schema_json } } }
|
205
|
+
end
|
206
|
+
|
207
|
+
return nil if bedrock_tools.empty?
|
208
|
+
|
209
|
+
config = { tools: bedrock_tools }
|
210
|
+
|
211
|
+
# Map tool_choice
|
212
|
+
if tool_choice
|
213
|
+
if tool_choice.is_a?(String)
|
214
|
+
case tool_choice
|
215
|
+
when "auto"
|
216
|
+
config[:tool_choice] = { auto: {} }
|
217
|
+
when "any", "required"
|
218
|
+
config[:tool_choice] = { any: {} }
|
219
|
+
when "none"
|
220
|
+
return nil
|
221
|
+
end
|
222
|
+
elsif tool_choice.is_a?(Hash)
|
223
|
+
tc_type = tool_choice[:type] || tool_choice["type"]
|
224
|
+
if tc_type.to_s == "function"
|
225
|
+
fn = tool_choice[:function] || tool_choice["function"] || {}
|
226
|
+
name = fn[:name] || fn["name"]
|
227
|
+
config[:tool_choice] = { tool: { name: name } } if name
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
config
|
233
|
+
end
|
234
|
+
|
235
|
+
def convert_response(response)
|
236
|
+
tool_calls = []
|
237
|
+
text = +""
|
238
|
+
|
239
|
+
(response.output&.message&.content || []).each do |c|
|
240
|
+
text << c.text.to_s if c.respond_to?(:text) && c.text
|
241
|
+
next unless c.respond_to?(:tool_use) && c.tool_use
|
242
|
+
|
243
|
+
tu = c.tool_use
|
244
|
+
args_json = begin
|
245
|
+
input = tu.input
|
246
|
+
# Ensure we have a proper hash with string keys
|
247
|
+
if input.is_a?(Hash)
|
248
|
+
stringified_input = JSON.parse(JSON.generate(input))
|
249
|
+
JSON.dump(stringified_input)
|
250
|
+
elsif input.is_a?(String)
|
251
|
+
input
|
252
|
+
else
|
253
|
+
JSON.dump(input || {})
|
254
|
+
end
|
255
|
+
rescue StandardError => e
|
256
|
+
warn "Error converting tool input: #{e.message}"
|
257
|
+
"{}"
|
258
|
+
end
|
259
|
+
|
260
|
+
tool_calls << {
|
261
|
+
"id" => tu.tool_use_id,
|
262
|
+
"type" => "function",
|
263
|
+
"function" => { "name" => tu.name, "arguments" => args_json }
|
264
|
+
}
|
265
|
+
end
|
266
|
+
|
267
|
+
{
|
268
|
+
"choices" => [
|
269
|
+
{
|
270
|
+
"message" => begin
|
271
|
+
msg = { "role" => "assistant" }
|
272
|
+
msg["content"] = (tool_calls.empty? ? text : nil)
|
273
|
+
msg["tool_calls"] = tool_calls if tool_calls.any?
|
274
|
+
msg
|
275
|
+
end
|
276
|
+
}
|
277
|
+
],
|
278
|
+
"usage" => {
|
279
|
+
"input_tokens" => response.usage&.input_tokens,
|
280
|
+
"output_tokens" => response.usage&.output_tokens,
|
281
|
+
"total_tokens" => response.usage&.total_tokens
|
282
|
+
}
|
283
|
+
}
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
data/lib/raix/bedrock.rb
ADDED
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: raix-bedrock
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Martin Emde
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: aws-sdk-bedrockruntime
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: raix
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
description: AWS Bedrock provider for Raix with tool and MCP support.
|
41
|
+
email:
|
42
|
+
- me@martinemde.com
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- CHANGELOG.md
|
48
|
+
- LICENSE.txt
|
49
|
+
- README.md
|
50
|
+
- Rakefile
|
51
|
+
- lib/raix/bedrock.rb
|
52
|
+
- lib/raix/bedrock/openai_proxy.rb
|
53
|
+
- lib/raix/bedrock/provider.rb
|
54
|
+
- lib/raix/bedrock/version.rb
|
55
|
+
- sig/raix/bedrock.rbs
|
56
|
+
homepage: https://github.com/martinemde/raix-bedrock
|
57
|
+
licenses:
|
58
|
+
- MIT
|
59
|
+
metadata:
|
60
|
+
allowed_push_host: https://rubygems.org
|
61
|
+
homepage_uri: https://github.com/martinemde/raix-bedrock
|
62
|
+
source_code_uri: https://github.com/martinemde/raix-bedrock
|
63
|
+
changelog_uri: https://github.com/martinemde/raix-bedrock/blob/main/CHANGELOG.md
|
64
|
+
rdoc_options: []
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: 3.2.0
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubygems_version: 3.6.9
|
79
|
+
specification_version: 4
|
80
|
+
summary: AWS Bedrock provider for Raix.
|
81
|
+
test_files: []
|