Skip to content

feat: integrates Aws bedrock into golem #27

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b66461d
feat: integrated aws bedrock in golem-llm
iambenkay Jun 11, 2025
d58b1f0
feat: ran llm-test and clippy
iambenkay Jun 12, 2025
0e6f1bd
feat: add nopoll feature flag to remove pollable when using async run…
iambenkay Jun 13, 2025
b3570c4
feat: extract bedrock client into state to reduce connection overhead
iambenkay Jun 13, 2025
c2b5916
chore: update readme with bedrock profiles
iambenkay Jun 13, 2025
3222373
chore: fix cargo check error with const refcell
iambenkay Jun 14, 2025
2fd0641
feat: consume last finish event which contains finish_reason
iambenkay Jun 14, 2025
0ae50d8
chore: add clarifying comment in durable_impl
iambenkay Jun 15, 2025
7291e77
chore: cleaned up redundant unwraps to make error handling more decla…
iambenkay Jun 15, 2025
a286788
chore: removed trailing whitespace
iambenkay Jun 16, 2025
70a0146
fix: change retry_prompt impl for bedrock to make message resumption …
iambenkay Jun 19, 2025
348323d
feat: switch to wasi-async-runtime from tokio
iambenkay Jul 4, 2025
8686a0c
chore: fix clippy suggestions
iambenkay Jul 4, 2025
d68436b
feat: implement wasi_client for aws operations. Use reqwest based was…
iambenkay Jul 9, 2025
4747501
chore: fix clippy suggestions
iambenkay Jul 9, 2025
fccd5ba
chore: remove transmute in favor of type from wasi crate
iambenkay Jul 9, 2025
bd14bbd
feat: use reader for passing body to avoid expensive clone
iambenkay Jul 12, 2025
db1d9b3
fix: change read implementation for body_reader to track position
iambenkay Jul 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,280 changes: 1,267 additions & 13 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ resolver = "2"

members = [
"llm/llm",
"llm/bedrock",
"llm/anthropic",
"llm/grok",
"llm/ollama",
Expand All @@ -16,9 +17,9 @@ lto = true
opt-level = 's'

[workspace.dependencies]
golem-llm = { path = "llm/llm", version = "0.0.0", default-features = false }
golem-rust = "1.6.0"
log = "0.4.27"
golem-llm = { path = "llm/llm", version = "0.0.0", default-features = false }
reqwest = { git = "https://github.com/golemcloud/reqwest", branch = "update-may-2025", features = [
"json",
] }
Expand Down
4 changes: 2 additions & 2 deletions Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ script = '''

is_portable = eq ${1} "--portable"

targets = array llm_openai llm_anthropic llm_grok llm_openrouter llm_ollama
targets = array llm_openai llm_anthropic llm_grok llm_openrouter llm_ollama llm_bedrock
for target in ${targets}
if is_portable
cp target/wasm32-wasip1/debug/golem_${target}.wasm components/debug/golem_${target}-portable.wasm
Expand All @@ -153,7 +153,7 @@ script = '''

is_portable = eq ${1} "--portable"

targets = array llm_openai llm_anthropic llm_grok llm_openrouter llm_ollama
targets = array llm_openai llm_anthropic llm_grok llm_openrouter llm_ollama llm_bedrock
for target in ${targets}
if is_portable
cp target/wasm32-wasip1/release/golem_${target}.wasm components/release/golem_${target}-portable.wasm
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ There are 8 published WASM files for each release:
| `golem-llm-grok.wasm` | LLM implementation for xAI (Grok), using custom Golem specific durability features |
| `golem-llm-openai.wasm` | LLM implementation for OpenAI, using custom Golem specific durability features |
| `golem-llm-openrouter.wasm` | LLM implementation for OpenRouter, using custom Golem specific durability features |
| `golem-llm-bedrock.wasm` | LLM implementation for Amazon Bedrock, using custom Golem specific durability features |
| `golem-llm-anthropic-portable.wasm` | LLM implementation for Anthropic AI, with no Golem specific dependencies. |
| `golem-llm-ollama-portable.wasm` | LLM implementation for Ollama, with no Golem specific dependencies. |
| `golem-llm-grok-portable.wasm` | LLM implementation for xAI (Grok), with no Golem specific dependencies. |
| `golem-llm-openai-portable.wasm` | LLM implementation for OpenAI, with no Golem specific dependencies. |
| `golem-llm-openrouter-portable.wasm` | LLM implementation for OpenRouter, with no Golem specific dependencies. |
| `golem-llm-bedrock-portable.wasm` | LLM implementation for Amazon Bedrock, with no Golem specific dependencies. |

Every component **exports** the same `golem:llm` interface, [defined here](wit/golem-llm.wit).

Expand All @@ -37,6 +39,7 @@ Each provider has to be configured with an API key passed as an environment vari
| OpenAI | `OPENAI_API_KEY` |
| OpenRouter | `OPENROUTER_API_KEY` |
| Ollama | `GOLEM_OLLAMA_BASE_URL` |
| Amazon Bedrock | `AWS_ACCESS_KEY_ID`, `AWS_REGION`, `AWS_SECRET_ACCESS_KEY` and `AWS_SESSION_TOKEN` (optional) |

Additionally, setting the `GOLEM_LLM_LOG=trace` environment variable enables trace logging for all the communication
with the underlying LLM provider.
Expand Down Expand Up @@ -145,6 +148,8 @@ Then build and deploy the _test application_. Select one of the following profil
| `openai-release` | Uses the OpenAI LLM implementation and compiles the code in release profile |
| `openrouter-debug` | Uses the OpenRouter LLM implementation and compiles the code in debug profile |
| `openrouter-release` | Uses the OpenRouter LLM implementation and compiles the code in release profile |
| `bedrock-debug` | Uses the Amazon Bedrock LLM implementation and compiles the code in debug profile |
| `bedrock-release` | Uses the Amazon Bedrock LLM implementation and compiles the code in release profile |

```bash
cd test
Expand Down
34 changes: 33 additions & 1 deletion llm/Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ skip_core_tasks = true
[tasks.build]
run_task = { name = [
"build-anthropic",
"build-bedrock",
"build-grok",
"build-openai",
"build-openrouter",
Expand All @@ -14,6 +15,7 @@ run_task = { name = [
[tasks.build-portable]
run_task = { name = [
"build-anthropic-portable",
"build-bedrock-portable",
"build-grok-portable",
"build-openai-portable",
"build-openrouter-portable",
Expand All @@ -23,6 +25,7 @@ run_task = { name = [
[tasks.release-build]
run_task = { name = [
"release-build-anthropic",
"release-build-bedrock",
"release-build-grok",
"release-build-openai",
"release-build-openrouter",
Expand All @@ -32,6 +35,7 @@ run_task = { name = [
[tasks.release-build-portable]
run_task = { name = [
"release-build-anthropic-portable",
"release-build-bedrock-portable",
"release-build-grok-portable",
"release-build-openai-portable",
"release-build-openrouter-portable",
Expand All @@ -49,6 +53,17 @@ install_crate = { crate_name = "cargo-component", version = "0.20.0" }
command = "cargo-component"
args = ["build", "-p", "golem-llm-ollama", "--no-default-features"]

[tasks.build-bedrock]
install_crate = { crate_name = "cargo-component", version = "0.20.0" }
command = "cargo-component"
args = ["build", "-p", "golem-llm-bedrock"]


[tasks.build-bedrock-portable]
install_crate = { crate_name = "cargo-component", version = "0.20.0" }
command = "cargo-component"
args = ["build", "-p", "golem-llm-bedrock", "--no-default-features"]


[tasks.build-anthropic]
install_crate = { crate_name = "cargo-component", version = "0.20.0" }
Expand Down Expand Up @@ -100,6 +115,21 @@ install_crate = { crate_name = "cargo-component", version = "0.20.0" }
command = "cargo-component"
args = ["build", "-p", "golem-llm-ollama", "--release", "--no-default-features"]

[tasks.release-build-bedrock]
install_crate = { crate_name = "cargo-component", version = "0.20.0" }
command = "cargo-component"
args = ["build", "-p", "golem-llm-bedrock", "--release"]

[tasks.release-build-bedrock-portable]
install_crate = { crate_name = "cargo-component", version = "0.20.0" }
command = "cargo-component"
args = [
"build",
"-p",
"golem-llm-bedrock",
"--release",
"--no-default-features",
]

[tasks.release-build-anthropic]
install_crate = { crate_name = "cargo-component", version = "0.20.0" }
Expand Down Expand Up @@ -163,7 +193,7 @@ dependencies = ["wit-update"]

script_runner = "@duckscript"
script = """
modules = array llm openai anthropic grok openrouter ollama
modules = array llm openai anthropic grok openrouter ollama bedrock

for module in ${modules}
rm -r ${module}/wit/deps
Expand Down Expand Up @@ -207,4 +237,6 @@ golem-cli app clean
golem-cli app build -b openrouter-debug
golem-cli app clean
golem-cli app build -b ollama-debug
golem-cli app clean
golem-cli app build -b bedrock-debug
'''
2 changes: 1 addition & 1 deletion llm/anthropic/src/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ pub fn process_response(response: MessagesResponse) -> ChatEvent {
Err(e) => {
return ChatEvent::Error(Error {
code: ErrorCode::InvalidRequest,
message: format!("Failed to decode base64 image data: {}", e),
message: format!("Failed to decode base64 image data: {e}"),
provider_error_json: None,
});
}
Expand Down
67 changes: 67 additions & 0 deletions llm/bedrock/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
[package]
name = "golem-llm-bedrock"
version = "0.0.0"
edition = "2021"
license = "Apache-2.0"
homepage = "https://golem.cloud"
repository = "https://github.com/golemcloud/golem-llm"
description = "WebAssembly component for working with Amazon Bedrock APIs, with special support for Golem Cloud"

[dependencies]
# AWS SDK crates
aws-config = { version = "1.5.19", default-features = false, features = [
"behavior-version-latest",
] }
aws-types = { version = "1.3.4", default-features = false }
aws-smithy-wasm = { version = "0.1.4", default-features = false }
aws-sdk-bedrockruntime = { version = "1.56.0", default-features = false }
aws-smithy-types = { version = "1.3.1" }
aws-smithy-runtime-api = "1.8.3"

wasi-async-runtime = "0.1.2"
wasi = "0.12.1+wasi-0.2.0"

# To infer mime types of downloaded images before passing to bedrock
infer = { version = "0.19.0", default-features = false }

golem-llm = { workspace = true }

golem-rust = { workspace = true }
log = { workspace = true }
reqwest = { git = "https://github.com/golemcloud/reqwest", branch = "update-july-2025", features = [
"json",
"async",
] }
serde = { workspace = true }
serde_json = { workspace = true }
wit-bindgen-rt = { workspace = true }
base64 = { workspace = true }
bytes = "1.10.1"

[lib]
crate-type = ["cdylib"]
path = "src/lib.rs"

[features]
default = ["durability"]
durability = [
"golem-rust/durability",
"golem-llm/durability",
"golem-llm/nopoll",
]

[package.metadata.component]
package = "golem:llm-bedrock"

[package.metadata.component.bindings]
generate_unused_types = true

[package.metadata.component.bindings.with]
"golem:llm/llm@1.0.0" = "golem_llm::golem::llm::llm"

[package.metadata.component.target]
path = "wit"

[package.metadata.component.target.dependencies]
"golem:llm" = { path = "wit/deps/golem-llm" }
"wasi:io" = { path = "wit/deps/wasi:io" }
49 changes: 49 additions & 0 deletions llm/bedrock/src/async_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::future::Future;

pub fn get_async_runtime() -> AsyncRuntime {
AsyncRuntime
}

pub struct AsyncRuntime;

impl AsyncRuntime {
pub fn block_on<F, Fut>(self, f: F) -> Fut::Output
where
F: FnOnce(wasi_async_runtime::Reactor) -> Fut,
Fut: Future,
{
wasi_async_runtime::block_on(f)
}
}

#[derive(Clone)]
pub struct UnsafeFuture<Fut> {
inner: Fut,
}

impl<F> UnsafeFuture<F>
where
F: Future,
{
pub fn new(inner: F) -> Self {
Self { inner }
}
}

unsafe impl<F> Send for UnsafeFuture<F> where F: Future {}
unsafe impl<F> Sync for UnsafeFuture<F> where F: Future {}

impl<F> Future for UnsafeFuture<F>
where
F: Future,
{
type Output = F::Output;

fn poll(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
let pinned_future = unsafe { self.as_mut().map_unchecked_mut(|this| &mut this.inner) };
pinned_future.poll(cx)
}
}
52 changes: 52 additions & 0 deletions llm/bedrock/src/bindings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Generated by `wit-bindgen` 0.36.0. DO NOT EDIT!
// Options used:
// * runtime_path: "wit_bindgen_rt"
// * with "golem:llm/llm@1.0.0" = "golem_llm::golem::llm::llm"
// * generate_unused_types
use golem_llm::golem::llm::llm as __with_name0;
#[cfg(target_arch = "wasm32")]
#[link_section = "component-type:wit-bindgen:0.36.0:golem:llm-bedrock@1.0.0:llm-library:encoded world"]
#[doc(hidden)]
pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 1760] = *b"\
\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xde\x0c\x01A\x02\x01\
A\x02\x01BO\x01m\x04\x04user\x09assistant\x06system\x04tool\x04\0\x04role\x03\0\0\
\x01m\x06\x0finvalid-request\x15authentication-failed\x13rate-limit-exceeded\x0e\
internal-error\x0bunsupported\x07unknown\x04\0\x0aerror-code\x03\0\x02\x01m\x06\x04\
stop\x06length\x0atool-calls\x0econtent-filter\x05error\x05other\x04\0\x0dfinish\
-reason\x03\0\x04\x01m\x03\x03low\x04high\x04auto\x04\0\x0cimage-detail\x03\0\x06\
\x01k\x07\x01r\x02\x03urls\x06detail\x08\x04\0\x09image-url\x03\0\x09\x01p}\x01r\
\x03\x04data\x0b\x09mime-types\x06detail\x08\x04\0\x0cimage-source\x03\0\x0c\x01\
q\x02\x03url\x01\x0a\0\x06inline\x01\x0d\0\x04\0\x0fimage-reference\x03\0\x0e\x01\
q\x02\x04text\x01s\0\x05image\x01\x0f\0\x04\0\x0ccontent-part\x03\0\x10\x01ks\x01\
p\x11\x01r\x03\x04role\x01\x04name\x12\x07content\x13\x04\0\x07message\x03\0\x14\
\x01r\x03\x04names\x0bdescription\x12\x11parameters-schemas\x04\0\x0ftool-defini\
tion\x03\0\x16\x01r\x03\x02ids\x04names\x0earguments-jsons\x04\0\x09tool-call\x03\
\0\x18\x01ky\x01r\x04\x02ids\x04names\x0bresult-jsons\x11execution-time-ms\x1a\x04\
\0\x0ctool-success\x03\0\x1b\x01r\x04\x02ids\x04names\x0derror-messages\x0aerror\
-code\x12\x04\0\x0ctool-failure\x03\0\x1d\x01q\x02\x07success\x01\x1c\0\x05error\
\x01\x1e\0\x04\0\x0btool-result\x03\0\x1f\x01r\x02\x03keys\x05values\x04\0\x02kv\
\x03\0!\x01kv\x01ps\x01k$\x01p\x17\x01p\"\x01r\x07\x05models\x0btemperature#\x0a\
max-tokens\x1a\x0estop-sequences%\x05tools&\x0btool-choice\x12\x10provider-optio\
ns'\x04\0\x06config\x03\0(\x01r\x03\x0cinput-tokens\x1a\x0doutput-tokens\x1a\x0c\
total-tokens\x1a\x04\0\x05usage\x03\0*\x01k\x05\x01k+\x01r\x05\x0dfinish-reason,\
\x05usage-\x0bprovider-id\x12\x09timestamp\x12\x16provider-metadata-json\x12\x04\
\0\x11response-metadata\x03\0.\x01p\x19\x01r\x04\x02ids\x07content\x13\x0atool-c\
alls0\x08metadata/\x04\0\x11complete-response\x03\01\x01r\x03\x04code\x03\x07mes\
sages\x13provider-error-json\x12\x04\0\x05error\x03\03\x01q\x03\x07message\x012\0\
\x0ctool-request\x010\0\x05error\x014\0\x04\0\x0achat-event\x03\05\x01k\x13\x01k\
0\x01r\x02\x07content7\x0atool-calls8\x04\0\x0cstream-delta\x03\09\x01q\x03\x05d\
elta\x01:\0\x06finish\x01/\0\x05error\x014\0\x04\0\x0cstream-event\x03\0;\x04\0\x0b\
chat-stream\x03\x01\x01h=\x01p<\x01k?\x01@\x01\x04self>\0\xc0\0\x04\0\x1c[method\
]chat-stream.get-next\x01A\x01@\x01\x04self>\0?\x04\0%[method]chat-stream.blocki\
ng-get-next\x01B\x01p\x15\x01@\x02\x08messages\xc3\0\x06config)\06\x04\0\x04send\
\x01D\x01o\x02\x19\x20\x01p\xc5\0\x01@\x03\x08messages\xc3\0\x0ctool-results\xc6\
\0\x06config)\06\x04\0\x08continue\x01G\x01i=\x01@\x02\x08messages\xc3\0\x06conf\
ig)\0\xc8\0\x04\0\x06stream\x01I\x04\0\x13golem:llm/llm@1.0.0\x05\0\x04\0#golem:\
llm-bedrock/llm-library@1.0.0\x04\0\x0b\x11\x01\0\x0bllm-library\x03\0\0\0G\x09p\
roducers\x01\x0cprocessed-by\x02\x0dwit-component\x070.220.0\x10wit-bindgen-rust\
\x060.36.0";
#[inline(never)]
#[doc(hidden)]
pub fn __link_custom_section_describing_imports() {
wit_bindgen_rt::maybe_link_cabi_realloc();
}
Loading
Loading