Skip to content
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

hellovai/go #1520

Draft
wants to merge 2 commits into
base: canary
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions engine/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"language_client_python",
"language_client_ruby/ext/ruby_ffi",
"language_client_typescript",
"language_client_go/lib/baml",
"sandbox",
]
default-members = [
Expand All @@ -28,6 +29,7 @@ default-members = [
"language_client_python",
"language_client_ruby/ext/ruby_ffi",
"language_client_typescript",
"language_client_go/lib/baml",
]

[workspace.dependencies]
Expand Down
8 changes: 8 additions & 0 deletions engine/language_client_go/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
main
*.dylib
*.so
*.dll
*.lib
*.exp
*.def
*.res
83 changes: 83 additions & 0 deletions engine/language_client_go/lib/baml.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#ifndef RUST_CFFI_H
#define RUST_CFFI_H

#ifdef __cplusplus
extern "C" {
#endif

#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>

/*
* Struct representing keyword arguments passed from C to Rust.
* - `len` is the number of key/value pairs.
* - `keys` is an array of null-terminated strings (the keys).
* - `values` is an array of null-terminated strings (the JSON-encoded values).
*/
typedef struct CKwargs {
size_t len;
const char **keys;
const char **values;
} CKwargs;

/*
* Callback function type.
* The callback receives a pointer to a null-terminated C string containing the JSON result.
* Note: The returned string is allocated by Rust and must be freed using free_string.
*/
typedef void (*ResultCallback)(const char *result);

/*
* Extern "C" functions exported from the Rust CFFI layer.
*/

// Prints a hello message. `name` must be a null-terminated string.
void hello(const char *name);

// Prints a whispered message. `message` must be a null-terminated string.
void whisper(const char *message);

// Creates and returns a pointer to a Baml runtime instance.
const void* create_baml_runtime(void);

// Destroys a previously created Baml runtime instance.
void destroy_baml_runtime(const void *runtime);

/*
* Calls a function in the Baml runtime.
*
* Parameters:
* - runtime: a pointer to the runtime (as returned by create_baml_runtime).
* - function_name: the name of the function to call (null-terminated string).
* - kwargs: pointer to a CKwargs structure containing keyword arguments.
* - callback: a function to be called with the result.
*
* The callback receives a pointer to a C string (JSON) that must later be freed with free_string.
*/
void call_function_from_c(const void *runtime,
const char *function_name,
const CKwargs *kwargs,
ResultCallback callback);

// Invokes the runtime CLI. `args` is a null-terminated array of null-terminated C strings.
void invoke_runtime_cli(const char * const* args);

/*
* Frees a C string that was allocated by the Rust runtime (e.g., in call_function_from_c).
* Call this function on any string returned via a callback once it is no longer needed.
*/
void free_string(char *s);


// In baml.h
typedef void (*callback_func)(char*);
extern bool register_callback(uint32_t id, callback_func callback);
extern bool unregister_callback(uint32_t id);
extern bool trigger_callback(uint32_t id, char* message);

#ifdef __cplusplus
}
#endif

#endif // RUST_CFFI_H
25 changes: 25 additions & 0 deletions engine/language_client_go/lib/baml/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "hello"
version = "0.1.0"
edition = "2021"

[lib]
# If you only wanted dynamic library, you'd use only "cdylib".
# If you only wanted static library, you'd use only "staticlib".
# This demo shows both. See https://doc.rust-lang.org/reference/linkage.html
# for more information.
crate-type = ["cdylib", "staticlib"]

[dependencies]
libc = "0.2.2"
baml-cli.workspace = true
baml-types.workspace = true
baml-runtime = { path = "../../../baml-runtime", default-features = false, features = [
"internal",
] }
internal-baml-codegen.workspace = true
serde.workspace = true
serde_json.workspace = true
tokio = { version = "1", features = ["full"] }
tokio-util = { version = "0.7", features = ["full"] }
once_cell.workspace = true
191 changes: 191 additions & 0 deletions engine/language_client_go/lib/baml/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
use std::{ffi::CStr, path::Path};

extern crate baml_runtime;
use baml_runtime::BamlRuntime;

#[no_mangle]
pub extern "C" fn hello(name: *const libc::c_char) {
let name_cstr = unsafe { CStr::from_ptr(name) };
let name = name_cstr.to_str().unwrap();
println!("Hello {}!", name);
}

#[no_mangle]
pub extern "C" fn whisper(message: *const libc::c_char) {
let message_cstr = unsafe { CStr::from_ptr(message) };
let message = message_cstr.to_str().unwrap();
println!("({})", message);
}

#[no_mangle]
pub extern "C" fn create_baml_runtime() -> *const libc::c_void {
const BAML_DIR: &str = "/Users/vbv/repos/gloo-lang/integ-tests/baml_src";
let env_vars = std::env::vars().into_iter().map(|(k, v)| (k.to_string(), v.to_string())).collect();
let runtime = BamlRuntime::from_directory(&Path::new(BAML_DIR), env_vars);
Box::into_raw(Box::new(runtime)) as *const libc::c_void
}

#[no_mangle]
pub extern "C" fn destroy_baml_runtime(runtime: *const libc::c_void) {
unsafe {
let _ = Box::from_raw(runtime as *mut BamlRuntime);
}
}

#[no_mangle]
pub extern "C" fn invoke_runtime_cli(args: *const *const libc::c_char) {
// Safety: We assume `args` is a valid pointer to a null-terminated array of C strings.
let args_vec = unsafe {
// Ensure the pointer itself is not null.
if args.is_null() {
Vec::new()
} else {
let mut vec = Vec::new();
let mut i = 0;
// Iterate until a null pointer is encountered.
while !(*args.add(i)).is_null() {
let c_str = CStr::from_ptr(*args.add(i));
// Convert to Rust String (lossy conversion handles non-UTF8 gracefully).
vec.push(c_str.to_string_lossy().into_owned());
i += 1;
}
vec
}
};
baml_cli::run_cli(
args_vec,
baml_runtime::RuntimeCliDefaults {
output_type: baml_types::GeneratorOutputType::PythonPydantic,
},
)
.unwrap();
}

use std::ffi::CString;
use std::os::raw::c_char;
use std::ptr;

use baml_types::{BamlMap, BamlValue};

#[repr(C)]
pub struct CKwargs {
pub len: libc::size_t,
pub keys: *const *const c_char,
pub values: *const *const c_char,
}

/// Convert CKwargs to a BamlMap<String, BamlValue>
unsafe fn ckwargs_to_map(kwargs: *const CKwargs) -> BamlMap<String, BamlValue> {
let mut map = BamlMap::new();
if kwargs.is_null() {
return map;
}
let kwargs_ref = &*kwargs;
for i in 0..(kwargs_ref.len as isize) {
let key_ptr = *kwargs_ref.keys.offset(i);
let value_ptr = *kwargs_ref.values.offset(i);
if let (Ok(key), Ok(value)) = (
CStr::from_ptr(key_ptr).to_str(),
serde_json::from_str::<BamlValue>(CStr::from_ptr(value_ptr).to_str().unwrap()),
) {
map.insert(key.to_owned(), value.to_owned());
}
}
map
}

/// Type for the callback function.
/// The callback receives a pointer to a C string containing the JSON result.
pub type ResultCallback = extern "C" fn(result: *const c_char);

/// Extern "C" function that returns immediately, scheduling the async call.
/// Once the asynchronous function completes, the provided callback is invoked.
#[no_mangle]
pub extern "C" fn call_function_from_c(
runtime: *const libc::c_void,
function_name: *const c_char,
kwargs: *const CKwargs,
callback: ResultCallback,
) {
// Safety: assume that the pointers provided are valid.
let runtime = unsafe { &*(runtime as *const BamlRuntime) };

// Convert the function name.
let func_name = match unsafe { CStr::from_ptr(function_name) }.to_str() {
Ok(s) => s.to_owned(),
Err(_) => {
callback(ptr::null());
return;
}
};

// Convert keyword arguments.
let keyword_args = unsafe { ckwargs_to_map(kwargs) };

let ctx = runtime.create_ctx_manager(BamlValue::String("cffi".to_string()), None);

// Spawn an async task to await the future and call the callback when done.
// Ensure that a Tokio runtime is running in your application.
tokio::spawn(async move {
let future = runtime.call_function(func_name, &keyword_args, &ctx, None, None);
let (result, _) = future.await;
let result_str = match result {
Ok(result) => result.to_string(),
Err(_) => String::new(),
};
let c_result = CString::new(result_str).unwrap();
callback(c_result.into_raw());
// Note: Responsibility for freeing the returned string lies with the caller.
});
}


// This is present so it's easy to test that the code works natively in Rust via `cargo test`
#[cfg(test)]
pub mod test {

use std::ffi::CString;
use super::*;

// This is meant to do the same stuff as the main function in the .go files
#[test]
fn simulated_main_function () {
hello(CString::new("world").unwrap().into_raw());
whisper(CString::new("this is code from Rust").unwrap().into_raw());
}
}

// In your Rust code that becomes libhello.dylib
use std::collections::HashMap;
use std::sync::Mutex;
use once_cell::sync::Lazy;

// Using a static HashMap to store multiple callbacks with an ID
static CALLBACKS: Lazy<Mutex<HashMap<u32, extern "C" fn(*const libc::c_char)>>> =
Lazy::new(|| Mutex::new(HashMap::new()));

#[no_mangle]
pub extern "C" fn register_callback(id: u32, callback: extern "C" fn(*const libc::c_char)) -> bool {
let mut callbacks = CALLBACKS.lock().unwrap();
callbacks.insert(id, callback);
true
}

#[no_mangle]
pub extern "C" fn unregister_callback(id: u32) -> bool {
let mut callbacks = CALLBACKS.lock().unwrap();
callbacks.remove(&id).is_some()
}

#[no_mangle]
pub extern "C" fn trigger_callback(id: u32, message: *const libc::c_char) -> bool {
let callbacks = CALLBACKS.lock().unwrap();
if let Some(callback) = callbacks.get(&id) {
unsafe {
callback(message);
}
true
} else {
false
}
}
Loading
Loading