openfunctions-rs 0.1.0

A universal framework for creating and managing LLM tools and agents
Documentation
//! Core functionality for OpenFunctions.
//!
//! This module provides the central components of the framework, including the
//! structures for tools, agents, configuration, and execution. It also contains
//! core utilities like the `Registry` for managing tools and agents, and the
//! `TestRunner` for quality assurance.

use anyhow::Result;
use std::collections::HashMap;
use tracing::{error, info};

pub mod agent;
pub mod builder;
pub mod checker;
pub mod config;
pub mod function;
pub mod loader;
pub mod registry;
pub mod tool;

pub use agent::Agent;
pub use builder::Builder;
pub use checker::Checker;
pub use config::Config;
pub use function::FunctionDeclaration;
pub use loader::Loader;
pub use registry::Registry;
pub use tool::Tool;

/// A test runner for executing tests on tools and agents.
///
/// `TestRunner` discovers and runs tests, reporting the results.
pub struct TestRunner {
    config: Config,
    registry: Registry,
}

impl TestRunner {
    /// Creates a new `TestRunner` with the given configuration.
    pub fn new(config: &Config) -> Self {
        Self {
            config: config.clone(),
            registry: Registry::new(),
        }
    }

    /// Runs all tests for all registered tools and agents.
    pub async fn run_all(&mut self) -> Result<TestResults> {
        info!("Running all tests...");
        self.registry.load_tools(&self.config.tool_dirs).await?;
        self.registry.load_agents(&self.config.agent_dirs).await?;

        let mut results = TestResults::default();

        // Run tool tests
        for tool_name in self.registry.list_tools() {
            let result = self.run_tool_test(&tool_name).await?;
            results.add_tool_result(tool_name, result);
        }

        // Run agent tests
        for agent_name in self.registry.list_agents() {
            let result = self.run_agent_test(&agent_name).await?;
            results.add_agent_result(agent_name, result);
        }

        Ok(results)
    }

    /// Run tests for a specific suite.
    pub async fn run_suite(&mut self, suite: &str) -> Result<TestResults> {
        match suite {
            "tools" => self.run_tool_tests().await,
            "agents" => self.run_agent_tests().await,
            "integration" => self.run_integration_tests().await,
            _ => anyhow::bail!("Unknown test suite: {}", suite),
        }
    }

    async fn run_tool_tests(&mut self) -> Result<TestResults> {
        self.registry.load_tools(&self.config.tool_dirs).await?;
        let mut results = TestResults::default();
        for tool_name in self.registry.list_tools() {
            let result = self.run_tool_test(&tool_name).await?;
            results.add_tool_result(tool_name, result);
        }
        Ok(results)
    }

    async fn run_agent_tests(&mut self) -> Result<TestResults> {
        self.registry.load_agents(&self.config.agent_dirs).await?;
        let mut results = TestResults::default();
        for agent_name in self.registry.list_agents() {
            let result = self.run_agent_test(&agent_name).await?;
            results.add_agent_result(agent_name, result);
        }
        Ok(results)
    }

    async fn run_integration_tests(&self) -> Result<TestResults> {
        info!("Running integration tests...");
        // TODO: Implement integration tests.
        Ok(TestResults::default())
    }

    async fn run_tool_test(&self, tool: &str) -> Result<TestResult> {
        info!(tool = %tool, "Running test for tool");
        // TODO: Implement actual test execution logic for tools.
        Ok(TestResult {
            passed: true,
            duration: std::time::Duration::from_millis(100),
            output: format!("Tool {} test passed", tool),
        })
    }

    async fn run_agent_test(&self, agent: &str) -> Result<TestResult> {
        info!(agent = %agent, "Running test for agent");
        // TODO: Implement actual test execution logic for agents.
        Ok(TestResult {
            passed: true,
            duration: std::time::Duration::from_millis(200),
            output: format!("Agent {} test passed", agent),
        })
    }
}

/// A container for the results of a test run.
#[derive(Debug, Default)]
pub struct TestResults {
    tool_results: HashMap<String, TestResult>,
    agent_results: HashMap<String, TestResult>,
}

impl TestResults {
    /// Adds a tool test result.
    pub fn add_tool_result(&mut self, tool_name: String, result: TestResult) {
        self.tool_results.insert(tool_name, result);
    }

    /// Adds an agent test result.
    pub fn add_agent_result(&mut self, agent_name: String, result: TestResult) {
        self.agent_results.insert(agent_name, result);
    }

    /// Prints a summary of the test results to the console.
    pub fn print_summary(&self) {
        info!("Test Results Summary:");

        let total_tools = self.tool_results.len();
        let passed_tools = self.tool_results.values().filter(|r| r.passed).count();
        info!("Tools: {}/{} passed", passed_tools, total_tools);

        let total_agents = self.agent_results.len();
        let passed_agents = self.agent_results.values().filter(|r| r.passed).count();
        info!("Agents: {}/{} passed", passed_agents, total_agents);

        if passed_tools < total_tools || passed_agents < total_agents {
            error!("Failed tests:");
            for (tool, result) in &self.tool_results {
                if !result.passed {
                    error!("  Tool '{}': {}", tool, result.output);
                }
            }
            for (agent, result) in &self.agent_results {
                if !result.passed {
                    error!("  Agent '{}': {}", agent, result.output);
                }
            }
        }
    }
}

/// Represents the result of a single test case.
#[derive(Debug)]
pub struct TestResult {
    /// Whether the test passed
    pub passed: bool,
    /// Duration of the test execution
    pub duration: std::time::Duration,
    /// Test output
    pub output: String,
}