Skip to content

Commit e3bede4

Browse files
committed
wip
1 parent b13d2f8 commit e3bede4

File tree

21 files changed

+688
-2
lines changed

21 files changed

+688
-2
lines changed

Cargo.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[workspace]
22
resolver = "2"
33

4-
members = [ "golem-llm/llm", "golem-llm/llm-anthropic", "golem-llm/llm-grok", "golem-llm/llm-ollama", "golem-llm/llm-openai", "golem-llm/llm-openrouter", "golem-web-search/web-search", "golem-web-search/websearch-google"]
4+
members = [ "golem-llm/llm", "golem-llm/llm-anthropic", "golem-llm/llm-grok", "golem-llm/llm-ollama", "golem-llm/llm-openai", "golem-llm/llm-openrouter", "golem-web-search/web-search", "golem-web-search/websearch-brave", "golem-web-search/websearch-google", "golem-web-search/websearch-serper", "golem-web-search/websearch-tavily"]
55

66
[profile.release]
77
debug = false

golem-web-search/Makefile.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,20 @@ install_crate = { crate_name = "cargo-component", version = "0.20.0" }
2020
command = "cargo-component"
2121
args = ["build", "-p", "golem-websearch-google", "--no-default-features"]
2222

23+
[tasks.build-brave]
24+
install_crate = { crate_name = "cargo-component", version = "0.20.0" }
25+
command = "cargo-component"
26+
args = ["build", "-p", "golem-websearch-brave"]
27+
28+
[tasks.build-brave-portable]
29+
install_crate = { crate_name = "cargo-component", version = "0.20.0" }
30+
command = "cargo-component"
31+
args = ["build", "-p", "golem-websearch-brave", "--no-default-features"]
32+
2333
[tasks.build]
2434
dependencies = [
2535
"build-google",
36+
"build-brave",
2637
]
2738

2839
[tasks.build-portable]

golem-web-search/test/components-rust/test-web-search/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ required-features = []
1010

1111
[features]
1212
default = ["google"]
13-
google =[]
13+
google = []
14+
brave = []
15+
tavily = []
16+
serper = []
1417

1518
[dependencies]
1619
# To use common shared libs, use the following:
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[package]
2+
name = "golem-web-search"
3+
version = "0.0.0"
4+
edition = "2021"
5+
license = "Apache-2.0"
6+
homepage = "https://golem.cloud"
7+
repository = "https://github.com/golemcloud/golem-llm"
8+
description = "WebAssembly components for working with AI models and providers APIs, with special support for Golem Cloud"
9+
10+
[lib]
11+
path = "src/lib.rs"
12+
crate-type = ["rlib"]
13+
14+
[dependencies]
15+
golem-rust = { workspace = true }
16+
log = { workspace = true }
17+
mime = "0.3.17"
18+
nom = { version = "7.1", default-features = false }
19+
reqwest = { workspace = true }
20+
thiserror = "2.0.12"
21+
wasi-logger = "0.1.2"
22+
wit-bindgen = { version = "0.40.0" }
23+
24+
[features]
25+
default = ["durability"]
26+
durability = ["golem-rust/durability"]
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use crate::exports::golem::web_search::web_search::{SearchError};
2+
use std::{collections::HashMap, ffi::OsStr};
3+
4+
/// Gets an expected configuration value from the environment, and fails if its is not found
5+
/// using the `fail` function. Otherwise, it runs `succeed` with the configuration value.
6+
pub fn with_config_key<R>(
7+
keys: &[impl AsRef<OsStr>],
8+
fail: impl FnOnce(SearchError) -> R,
9+
succeed: impl FnOnce(HashMap<String,String>) -> R,
10+
) -> R {
11+
let mut hashmap = HashMap::new();
12+
for key in keys {
13+
match std::env::var(key.as_ref()) {
14+
Ok(value) => {
15+
hashmap.insert(key.as_ref().to_string_lossy().to_string(), value);
16+
}
17+
Err(_) => {
18+
let error = SearchError::BackendError(format!("Missing config key: {}", key.as_ref().to_string_lossy()));
19+
return fail(error);
20+
}
21+
}
22+
}
23+
succeed(hashmap)
24+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use std::marker::PhantomData;
2+
3+
/// Wraps a web search implementation with custom durability
4+
pub struct DurableWebSearch<Impl> {
5+
phantom: PhantomData<Impl>,
6+
}
7+
8+
/// When the durability feature flag is off, wrapping with `DurableWebSearch` is just a passthrough
9+
#[cfg(not(feature = "durability"))]
10+
mod passthrough_impl {
11+
use crate::durability::DurableWebSearch;
12+
use crate::exports::golem::web_search::web_search::{
13+
Guest, SearchError, SearchMetadata, SearchParams, SearchResult, SearchSession,
14+
};
15+
16+
impl<Impl: Guest> Guest for DurableWebSearch<Impl> {
17+
type SearchSession = Impl::SearchSession;
18+
19+
fn start_search(params: SearchParams) -> Result<SearchSession, SearchError> {
20+
Impl::start_search(params)
21+
}
22+
23+
fn search_once(
24+
params: SearchParams,
25+
) -> Result<(Vec<SearchResult>, Option<SearchMetadata>), SearchError> {
26+
Impl::search_once(params)
27+
}
28+
}
29+
}
30+
31+
/// When the durability feature flag is on, wrapping with `DurableWebSearch` adds custom durability
32+
/// on top of the provider-specific web search implementation using Golem's special host functions and
33+
/// the `golem-rust` helper library.
34+
///
35+
/// There will be custom durability entries saved in the oplog, with the full web search request and configuration
36+
/// stored as input, and the full response stored as output. To serialize these in a way it is
37+
/// observable by oplog consumers, each relevant data type has to be converted to/from `ValueAndType`
38+
/// which is implemented using the type classes and builder in the `golem-rust` library.
39+
#[cfg(feature = "durability")]
40+
mod durable_impl {
41+
use crate::durability::DurableWebSearch;
42+
use crate::exports::golem::web_search::web_search::{
43+
Guest, SearchError, SearchMetadata, SearchParams, SearchResult, SearchSession,
44+
};
45+
use golem_rust::bindings::golem::durability::durability::DurableFunctionType;
46+
use golem_rust::durability::Durability;
47+
use golem_rust::{with_persistence_level, FromValueAndType, IntoValue, PersistenceLevel};
48+
use std::fmt::{Display, Formatter};
49+
50+
impl<Impl: Guest> Guest for DurableWebSearch<Impl> {
51+
type SearchSession = Impl::SearchSession;
52+
53+
fn start_search(params: SearchParams) -> Result<SearchSession, SearchError> {
54+
let durability = Durability::<NoOutput, UnusedError>::new(
55+
"golem_web_search",
56+
"start_search",
57+
DurableFunctionType::WriteRemote,
58+
);
59+
if durability.is_live() {
60+
let result = with_persistence_level(PersistenceLevel::PersistNothing, || {
61+
Impl::start_search(params.clone())
62+
});
63+
match result {
64+
Ok(session) => {
65+
let _ = durability.persist_infallible(params, NoOutput);
66+
Ok(session)
67+
}
68+
Err(err) => Err(err),
69+
}
70+
} else {
71+
let _: NoOutput = durability.replay_infallible();
72+
Impl::start_search(params)
73+
}
74+
}
75+
76+
fn search_once(
77+
params: SearchParams,
78+
) -> Result<(Vec<SearchResult>, Option<SearchMetadata>), SearchError> {
79+
let durability = Durability::<(Vec<SearchResult>, Option<SearchMetadata>), UnusedError>::new(
80+
"golem_web_search",
81+
"search_once",
82+
DurableFunctionType::WriteRemote,
83+
);
84+
if durability.is_live() {
85+
let result = with_persistence_level(PersistenceLevel::PersistNothing, || {
86+
Impl::search_once(params.clone())
87+
});
88+
match result {
89+
Ok(success) => Ok(durability.persist_infallible(params, success)),
90+
Err(err) => Err(err),
91+
}
92+
} else {
93+
let result: (Vec<SearchResult>, Option<SearchMetadata>) =
94+
durability.replay_infallible();
95+
Ok(result)
96+
}
97+
}
98+
}
99+
100+
#[derive(Debug, Clone, IntoValue, FromValueAndType)]
101+
struct NoOutput;
102+
103+
#[derive(Debug, IntoValue, FromValueAndType)]
104+
struct UnusedError;
105+
106+
impl Display for UnusedError {
107+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
108+
write!(f, "UnusedError")
109+
}
110+
}
111+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
use crate::exports::golem::web_search::web_search::SearchError;
3+
4+
/// Creates an `Error` value representing that something is unsuported
5+
pub fn unsupported(what: impl AsRef<str>) -> SearchError {
6+
SearchError::UnsupportedFeature(format!("Unsupported: {}", what.as_ref()))
7+
}
8+
9+
pub fn from_reqwest_error(details: impl AsRef<str>, err: reqwest::Error) -> SearchError {
10+
SearchError::BackendError(format!("{}: {err}", details.as_ref()))
11+
}
12+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
pub mod config;
2+
pub mod durability;
3+
pub mod error;
4+
5+
6+
wit_bindgen::generate!({
7+
path: "../wit",
8+
world: "web-search-library",
9+
generate_all,
10+
generate_unused_types: true,
11+
additional_derives: [PartialEq, golem_rust::FromValueAndType, golem_rust::IntoValue],
12+
pub_export_macro: true,
13+
});
14+
15+
pub use crate::exports::golem;
16+
pub use __export_web_search_library_impl as export_web_search;
17+
use std::cell::RefCell;
18+
use std::str::FromStr;
19+
20+
pub struct LoggingState {
21+
logging_initialized: bool,
22+
}
23+
24+
impl LoggingState {
25+
/// Initializes WASI logging based on the `GOLEM_WEB_SEARCH_LOG` environment variable.
26+
pub fn init(&mut self) {
27+
if !self.logging_initialized {
28+
let _ = wasi_logger::Logger::install();
29+
let max_level: log::LevelFilter =
30+
log::LevelFilter::from_str(&std::env::var("GOLEM_WEB_SEARCH_LOG").unwrap_or_default())
31+
.unwrap_or(log::LevelFilter::Info);
32+
log::set_max_level(max_level);
33+
self.logging_initialized = true;
34+
}
35+
}
36+
}
37+
38+
thread_local! {
39+
/// This holds the state of our application.
40+
pub static LOGGING_STATE: RefCell<LoggingState> = const { RefCell::new(LoggingState {
41+
logging_initialized: false,
42+
}) };
43+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "websearch-brave"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
wit-bindgen-rt = { workspace = true, features = ["bitflags"] }
8+
9+
[lib]
10+
crate-type = ["cdylib"]
11+
12+
[package.metadata.component]
13+
package = "component:websearch-brave"
14+
15+
[package.metadata.component.dependencies]

0 commit comments

Comments
 (0)