use std::env;
use tokio::runtime::Runtime;
use agio::{
AgentBuilder, Config, Error,
RegisteredTool, ToolRegistry, ToolDefinition,
tool_fn };
use async_trait::async_trait;
use rustls::crypto::CryptoProvider;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use uuid::Uuid;
pub struct ReverseToolTraditional;
#[async_trait]
impl RegisteredTool for ReverseToolTraditional {
fn definition(&self) -> ToolDefinition {
ToolDefinition {
name: "reverse_string_traditional".to_string(),
description: "Reverses a given string of text (traditional implementation).".to_string(),
strict: Some(true),
parameters: json!({
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "The string to reverse."
}
},
"required": ["text"],
"additionalProperties": false
}),
}
}
async fn execute(&self, arguments: Value) -> Result<String, Error> {
let text = arguments
.get("text")
.and_then(|v| v.as_str())
.ok_or_else(|| Error::Tool("Missing 'text' argument".to_string()))?;
let reversed: String = text.chars().rev().collect();
Ok(reversed)
}
}
#[derive(Debug, Serialize, Deserialize, schemars::JsonSchema)]
struct ReverseArgs {
text: String,
}
async fn reverse_string(args: ReverseArgs) -> Result<String, Error> {
let reversed: String = args.text.chars().rev().collect();
Ok(reversed)
}
#[test]
fn test_random_string_reverse_both_approaches() -> Result<(), Box<dyn std::error::Error>> {
let api_key = env::var("OPENAI_API_KEY")
.expect("Set the OPENAI_API_KEY env var before running this test.");
let random_uuid = Uuid::new_v4().to_string();
let reversed_uuid: String = random_uuid.chars().rev().collect();
let rt = Runtime::new()?;
let traditional_test = rt.block_on(async {
let registry = setup_traditional_tools().await;
test_approach(
"traditional",
&api_key,
&random_uuid,
&reversed_uuid,
registry
).await
})?;
let function_test = rt.block_on(async {
let registry = setup_function_tools().await;
test_approach(
"function-based",
&api_key,
&random_uuid,
&reversed_uuid,
registry
).await
})?;
Ok(())
}
#[test]
fn test_websocket_realtime_reverse() -> Result<(), Box<dyn std::error::Error>> {
rustls::crypto::ring::default_provider().install_default().unwrap();
let api_key = env::var("OPENAI_API_KEY")
.expect("Set the OPENAI_API_KEY env var before running this test.");
let random_uuid = Uuid::new_v4().to_string();
let reversed_uuid: String = random_uuid.chars().rev().collect();
let rt = Runtime::new()?;
rt.block_on(async {
let registry = setup_traditional_tools().await;
let config = Config::new()
.with_api_key(api_key)
.with_model("gpt-3.5-turbo") .with_max_tokens(300)
.with_temperature(0.0);
let mut agent = AgentBuilder::new()
.with_config(config)
.with_tools(registry)
.with_system_prompt(r#"You are a strict Reverser Assistant (Realtime test).
If the user requests text reversal, you MUST call the 'reverse_string_traditional' tool.
"#.to_string())
.with_websocket()? .build()?;
agent.connect_realtime("gpt-4-realtime-preview").await?;
println!("Connected to Realtime API over WebSocket.");
use agio::websocket_client::RealtimeEvent;
agent.send_realtime_event(&RealtimeEvent {
r#type: "test_hello".to_string(),
}).await?;
let user_msg = format!("Please reverse this text: {}", random_uuid);
println!("(Realtime) Attempting normal .run with text: {}", random_uuid);
let response = agent.run(&user_msg).await?;
println!("Got response: {}", response);
if response.contains(&reversed_uuid) {
println!("Realtime approach test passed!");
} else {
return Err(Error::Agent(format!(
"Realtime approach test failed: The final response '{response}' did not contain '{reversed_uuid}'"
)));
}
agent.close_realtime().await?;
println!("WebSocket closed gracefully.");
Ok(())
})?;
Ok(())
}
async fn setup_traditional_tools() -> ToolRegistry {
let mut registry = ToolRegistry::new();
registry.register(ReverseToolTraditional);
registry
}
async fn setup_function_tools() -> ToolRegistry {
let mut registry = ToolRegistry::new();
registry.register_fn(
"reverse_string",
"Reverses a given string of text (function-based implementation).",
reverse_string,
);
registry
}
async fn test_approach(
approach_name: &str,
api_key: &str,
random_uuid: &str,
reversed_uuid: &str,
registry: ToolRegistry
) -> Result<(), Error> {
println!("Testing {} approach", approach_name);
let config = Config::new()
.with_api_key(api_key.to_string())
.with_model("gpt-3.5-turbo") .with_max_tokens(300) .with_temperature(0.0);
let tool_name = if approach_name == "traditional" {
"reverse_string_traditional"
} else {
"reverse_string"
};
let mut agent = AgentBuilder::new()
.with_config(config)
.with_tools(registry)
.with_system_prompt(
format!(r#"You are a strict Reverser Assistant.
If the user requests text reversal, you MUST call the '{}' tool.
NEVER attempt to reverse text yourself.
After receiving the reversed text from the tool, include it in your response.
"#, tool_name)
)
.build()?;
let user_msg = format!("Please reverse this text: {}", random_uuid);
println!("Sending request to reverse: {}", random_uuid);
println!("Expected response should contain: {}", reversed_uuid);
let response = agent.run(&user_msg).await?;
println!("Got response: {}", response);
if response.contains(reversed_uuid) {
println!("{} approach test passed!", approach_name);
Ok(())
} else {
Err(Error::Agent(format!(
"{} approach test failed: The final response did not contain '{}'",
approach_name, reversed_uuid
)))
}
}