#openai #agent #ai #gpt #llm #websocket #websocket-client

agio

A Rust crate for configuring and using OpenAI in an agentic system

4 releases

new 0.0.1 Mar 4, 2025

#174 in Asynchronous

MIT license

74KB
1K SLoC

Agio: A Rust Client for OpenAI Agents

A Rust library for building agent-based systems with OpenAI's API.

Overview

Agio provides a structured interface for interacting with OpenAI's API, with a focus on tool calling capabilities and agent-based workflows. It handles conversation state management, token counting, tool execution, and now includes WebSocket support for OpenAI's "Realtime" Beta API.

Key Components

  • Agent: Main entry point for conversational interactions
  • OpenAIClient: Handles HTTP API communication
  • WebSocketClient: Supports OpenAI's "Realtime" Beta API
  • ToolRegistry: Manages the tools available to the agent
  • Config: Provides configuration options for API requests

Dependencies

This library has the following key dependencies:

  • Rust 2024 edition
  • tokio async runtime
  • serde/serde_json for serialization
  • reqwest for HTTP communication
  • tokio-tungstenite for WebSocket support
  • tiktoken-rs for token counting
  • thiserror for error handling

Installation

Add this to your Cargo.toml:

[dependencies]
agio = "0.1.0"

You'll need to provide your OpenAI API key to use this library.

Basic Usage

use agio::{Agent, AgentBuilder, Config};
use std::env;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Get API key from environment
    let api_key = env::var("OPENAI_API_KEY").expect("Missing API key");
    
    // Create agent with configuration
    let mut agent = AgentBuilder::new()
        .with_config(Config::new()
            .with_api_key(api_key)
            .with_model("gpt-4o")
            .with_max_tokens(1024))
        .with_system_prompt("You are a helpful assistant.")
        .build()?;

    // Run agent
    let response = agent.run("Tell me about Rust programming.").await?;
    println!("Response: {}", response);

    Ok(())
}

Adding Tools

Agio provides two ways to add tools to your agent:

Method 1: Implementing the RegisteredTool trait

use agio::{Agent, AgentBuilder, Config, Error};
use agio::tools::{RegisteredTool, ToolDefinition, ToolRegistry};
use async_trait::async_trait;
use serde_json::{json, Value};

struct WeatherTool;

#[async_trait]
impl RegisteredTool for WeatherTool {
    fn definition(&self) -> ToolDefinition {
        ToolDefinition {
            name: "get_weather".to_string(),
            description: "Get the current weather for a location".to_string(),
            parameters: json!({
                "type": "object",
                "required": ["location"],
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "The city and state, e.g., San Francisco, CA"
                    }
                },
                "additionalProperties": false
            }),
            strict: Some(true),
        }
    }

    async fn execute(&self, arguments: Value) -> Result<String, Error> {
        let location = arguments
            .get("location")
            .and_then(|v| v.as_str())
            .ok_or_else(|| Error::Tool("Missing location parameter".to_string()))?;
        
        // Here you would implement the actual weather lookup logic
        // This is just a placeholder
        Ok(format!("The weather in {} is sunny and 72°F", location))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let api_key = std::env::var("OPENAI_API_KEY").expect("Missing API key");
    
    // Create a tool registry and register the weather tool
    let mut tools = ToolRegistry::new();
    tools.register(WeatherTool);

    // Create agent with tools
    let mut agent = AgentBuilder::new()
        .with_config(Config::new()
            .with_api_key(api_key)
            .with_model("gpt-4o"))
        .with_system_prompt("You are a helpful assistant that can check the weather.")
        .with_tools(tools)
        .build()?;

    // Run agent
    let response = agent.run("What's the weather like in Seattle?").await?;
    println!("Response: {}", response);

    Ok(())
}

Method 2: Using the function-based approach

use agio::{Agent, AgentBuilder, Config, Error, tool_fn};
use agio::tools::ToolRegistry;
use serde::{Deserialize, Serialize};

// Define the arguments schema using schemars
#[derive(Debug, Serialize, Deserialize, schemars::JsonSchema)]
struct ReverseArgs {
    /// The string to reverse
    text: String,
}

// Define the function to be used as a tool
async fn reverse_string(args: ReverseArgs) -> Result<String, Error> {
    // Simply reverse the string
    let reversed: String = args.text.chars().rev().collect();
    Ok(reversed)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let api_key = std::env::var("OPENAI_API_KEY").expect("Missing API key");
    
    // Create a tool registry and register the tool using the tool_fn macro
    let mut tools = ToolRegistry::new();
    
    // Option 1: Using the tool_fn macro
    let reverse_tool = tool_fn!("reverse_string", "Reverses a given string of text", reverse_string);
    tools.register(reverse_tool);
    
    // Option 2: Using the register_fn method directly
    // tools.register_fn(
    //    "reverse_string",
    //    "Reverses a given string of text",
    //    reverse_string
    // );

    // Create agent with tools
    let mut agent = AgentBuilder::new()
        .with_config(Config::new()
            .with_api_key(api_key)
            .with_model("gpt-4o"))
        .with_system_prompt("You are a helpful assistant that can reverse text.")
        .with_tools(tools)
        .build()?;

    // Run agent
    let response = agent.run("Can you reverse this text: Hello World").await?;
    println!("Response: {}", response);

    Ok(())
}

Configuration Options

The library offers various configuration options:

// Example of configuring the OpenAI client
fn configure_client() -> Config {
    let config = Config::new()
        .with_api_key("your-api-key")
        .with_model("gpt-4o")         // Choose your model
        .with_temperature(0.7)        // Control randomness (0.0-2.0)
        .with_max_tokens(1024)        // Limit token generation
        .with_timeout(std::time::Duration::from_secs(30))  // Request timeout
        .with_base_url("https://api.openai.com/v1")  // API endpoint 
        .with_organization("your-org-id")  // Optional organization
        .with_stream(false)           // Enable/disable streaming
        .with_json_mode(false);       // Enable/disable JSON mode

    config
}

WebSocket Support for OpenAI "Realtime" Beta

Agio includes support for OpenAI's "Realtime" Beta API via WebSockets:

use agio::{Agent, AgentBuilder, Config, Error};
use agio::websocket_client::{RealtimeEvent, ServerEvent};
use agio::tools::ToolRegistry;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let api_key = std::env::var("OPENAI_API_KEY").expect("Missing API key");
    
    // Setup your tools registry
    let mut registry = ToolRegistry::new();
    // ... register your tools here
    
    // Create an agent with WebSocket support
    let mut agent = AgentBuilder::new()
        .with_config(Config::new()
            .with_api_key(api_key)
            .with_model("gpt-4o"))
        .with_tools(registry)
        .with_system_prompt("You are a helpful assistant.")
        .with_websocket()?  // Initialize WebSocket client
        .build()?;

    // Connect to the Realtime API
    agent.connect_realtime("gpt-4-realtime-preview").await?;
    
    // Send a custom event
    agent.send_realtime_event(&RealtimeEvent {
        r#type: "user_message".to_string(),
        // ... additional fields as needed
    }).await?;
    
    // Process incoming events
    agent.process_realtime_events(|event: ServerEvent| {
        println!("Received event: {:?}", event);
        Ok(())
    }).await?;
    
    // Close the connection when done
    agent.close_realtime().await?;

    Ok(())
}

Error Handling

The library uses the thiserror crate to provide detailed error types:

// Example of handling different error types
fn handle_response() -> Result<(), Box<dyn std::error::Error>> {
    let result = agent.run("Hello").await;

    match result {
        Ok(response) => println!("Response: {}", response),
        Err(err) => match err {
            Error::Request(msg) => eprintln!("Request error: {}", msg),
            Error::Tool(msg) => eprintln!("Tool execution error: {}", msg),
            Error::Config(msg) => eprintln!("Configuration error: {}", msg),
            Error::Parse(msg) => eprintln!("Parse error: {}", msg),
            Error::Agent(msg) => eprintln!("Agent error: {}", msg),
            _ => eprintln!("Other error: {}", err),
        },
    }

    Ok(())
}

Multi-turn Conversations

The agent maintains conversation state automatically:

// Example of a multi-turn conversation
async fn multi_turn_example() -> Result<(), Error> {
    // Agent setup code would be here...

    // First turn
    let response1 = agent.run("Hello, who are you?").await?;
    println!("Response 1: {}", response1);

    // Second turn (context is maintained)
    let response2 = agent.run("What did I just ask you?").await?;
    println!("Response 2: {}", response2);

    Ok(())
}

Token Management

The library includes utilities for token counting:

// Example of using token management utilities
fn token_utilities_example() -> Result<(), Error> {
    use agio::utils::count_tokens;
    use agio::utils::truncate_text_to_tokens;

    // Count tokens in text
    let text = "This is a sample text";
    let token_count = count_tokens(text, "gpt-4o")?;
    println!("Token count: {}", token_count);

    // Truncate text to fit within token limits
    let long_text = "A very long text that might exceed token limits...";
    let truncated = truncate_text_to_tokens(long_text, 50, "gpt-4o")?;
    println!("Truncated text: {}", truncated);
    
    Ok(())
}

Implementation Notes

  • There's built-in retry logic for transient API failures.
  • You can customize the agent's maximum conversation turns to prevent infinite loops.
  • WebSocket support for the OpenAI "Realtime" Beta API provides a foundation for real-time interaction.

Author

This library was created by Nikolas Yanek-Chrones (research@icarai.io).

Dependencies

~28–41MB
~571K SLoC