Files
goose/crates/goose-ffi/examples/goose_agent.py
meenalc e073e32178 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.
2025-04-16 16:53:03 -07:00

117 lines
3.5 KiB
Python

#!/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()