feat: Build a prototype FFI for goose rust library (#2206)

This builds a prototype FFI for goose rust library which only supports initializing goose agent and sending message, there is no support for tool calling yet in this library.
This commit is contained in:
meenalc
2025-04-16 16:53:03 -07:00
committed by GitHub
parent 0148ced33e
commit e073e32178
8 changed files with 910 additions and 19 deletions
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
"""
Python example for using the Goose FFI interface.
This example demonstrates how to:
1. Load the Goose FFI library
2. Create an agent with a provider
3. Add a tool extension
4. Send messages to the agent
5. Handle tool calls and responses
"""
import ctypes
import os
import platform
from ctypes import c_char_p, c_bool, c_uint32, c_void_p, Structure, POINTER
class ProviderType:
DATABRICKS = 0
# Platform-specific dynamic lib name
if platform.system() == "Darwin":
LIB_NAME = "libgoose_ffi.dylib"
elif platform.system() == "Linux":
LIB_NAME = "libgoose_ffi.so"
elif platform.system() == "Windows":
LIB_NAME = "goose_ffi.dll"
else:
raise RuntimeError("Unsupported platform")
# Adjust to your actual build output directory
LIB_PATH = os.path.join(os.path.dirname(__file__), "../../..", "target", "debug", LIB_NAME)
# Load library
goose = ctypes.CDLL(LIB_PATH)
# Forward declaration for goose_Agent
class goose_Agent(Structure):
pass
# Agent pointer type
goose_AgentPtr = POINTER(goose_Agent)
# C struct mappings
class ProviderConfig(Structure):
_fields_ = [
("provider_type", c_uint32),
("api_key", c_char_p),
("model_name", c_char_p),
("host", c_char_p),
]
class AsyncResult(Structure):
_fields_ = [
("succeeded", c_bool),
("error_message", c_char_p),
]
# Function signatures
goose.goose_agent_new.argtypes = [POINTER(ProviderConfig)]
goose.goose_agent_new.restype = goose_AgentPtr
goose.goose_agent_free.argtypes = [goose_AgentPtr]
goose.goose_agent_free.restype = None
goose.goose_agent_send_message.argtypes = [goose_AgentPtr, c_char_p]
goose.goose_agent_send_message.restype = c_void_p
goose.goose_free_string.argtypes = [c_void_p]
goose.goose_free_string.restype = None
goose.goose_free_async_result.argtypes = [POINTER(AsyncResult)]
goose.goose_free_async_result.restype = None
class GooseAgent:
def __init__(self, provider_type=ProviderType.DATABRICKS, api_key=None, model_name=None, host=None):
self.config = ProviderConfig(
provider_type=provider_type,
api_key=api_key.encode("utf-8") if api_key else None,
model_name=model_name.encode("utf-8") if model_name else None,
host=host.encode("utf-8") if host else None,
)
self.agent = goose.goose_agent_new(ctypes.byref(self.config))
if not self.agent:
raise RuntimeError("Failed to create Goose agent")
def __del__(self):
if getattr(self, "agent", None):
goose.goose_agent_free(self.agent)
def send_message(self, message: str) -> str:
msg = message.encode("utf-8")
response_ptr = goose.goose_agent_send_message(self.agent, msg)
if not response_ptr:
return "Error or NULL response from agent"
response = ctypes.string_at(response_ptr).decode("utf-8")
# Free the string using the proper C function provided by the library
# This correctly releases memory allocated by the Rust side
goose.goose_free_string(response_ptr)
return response
def main():
api_key = os.getenv("DATABRICKS_API_KEY")
host = os.getenv("DATABRICKS_HOST")
agent = GooseAgent(api_key=api_key, model_name="claude-3-7-sonnet", host=host)
print("Type a message (or 'quit' to exit):")
while True:
user_input = input("> ")
if user_input.lower() in ("quit", "exit"):
break
reply = agent.send_message(user_input)
print(f"Agent: {reply}\n")
if __name__ == "__main__":
main()